diff --git a/.github/workflows/broken_links_checker.yml b/.github/workflows/broken_links_checker.yml index 90488ca..ee8df76 100644 --- a/.github/workflows/broken_links_checker.yml +++ b/.github/workflows/broken_links_checker.yml @@ -1,35 +1,44 @@ -# Generated by Project Keeper -# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/broken_links_checker.yml +# This file was generated by Project Keeper. name: Broken Links Checker - on: schedule: - - cron: "0 5 * * 0" - + - { + cron: 0 5 * * 0 + } + workflow_dispatch: null jobs: linkChecker: runs-on: ubuntu-latest - permissions: + permissions: { contents: read + } defaults: - run: - shell: "bash" - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + run: { + shell: bash + } + concurrency: { + group: '${{ github.workflow }}-${{ github.ref }}', cancel-in-progress: true + } steps: - - uses: actions/checkout@v4 - - name: Configure broken links checker + - { + id: checkout, + uses: actions/checkout@v5 + } + - id: configure-broken-links-checker + name: Configure broken links checker run: | mkdir -p ./target echo '{"aliveStatusCodes": [429, 200], "ignorePatterns": [' \ - '{"pattern": "^https?://(www|dev).mysql.com/"},' \ - '{"pattern": "^https?://(www.)?opensource.org"}' \ - '{"pattern": "^https?://(www.)?eclipse.org"}' \ - '{"pattern": "^https?://projects.eclipse.org"}' \ - ']}' > ./target/broken_links_checker.json - - uses: gaurav-nelson/github-action-markdown-link-check@v1 - with: - use-quiet-mode: "yes" - use-verbose-mode: "yes" + '{"pattern": "^https?://(www|dev).mysql.com/"},' \ + '{"pattern": "^https?://(www.)?opensource.org"}' \ + '{"pattern": "^https?://(www.)?eclipse.org"}' \ + '{"pattern": "^https?://projects.eclipse.org"}' \ + ']}' > ./target/broken_links_checker.json + - id: run-broken-links-checker + uses: tcort/github-action-markdown-link-check@v1 + with: { + use-quiet-mode: yes, + use-verbose-mode: yes, config-file: ./target/broken_links_checker.json + } diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 7c087e9..aa273bf 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -3,7 +3,8 @@ name: CI Build on: push: branches: [ - main + main, + release/* ] pull_request: @@ -42,19 +43,22 @@ jobs: sudo rm -rf /usr/share/dotnet - name: Checkout the repository id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: { fetch-depth: 0 } - name: Set up JDKs id: setup-java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: |- 11 17 cache: maven + server-id: ossindex + server-username: OSSINDEX_USERNAME + server-password: OSSINDEX_API_TOKEN - name: Cache SonarCloud packages id: cache-sonar uses: actions/cache@v4 @@ -74,6 +78,10 @@ jobs: mvn --batch-mode clean verify \ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \ -DtrimStackTrace=false + env: { + OSSINDEX_USERNAME: '${{ secrets.OSSINDEX_USERNAME }}', + OSSINDEX_API_TOKEN: '${{ secrets.OSSINDEX_API_TOKEN }}' + } - name: Sonar analysis id: sonar-analysis if: ${{ env.SONAR_TOKEN != null }} @@ -110,7 +118,7 @@ jobs: '{"pattern": "^https?://(www.)?eclipse.org"}' \ '{"pattern": "^https?://projects.eclipse.org"}' \ ']}' > ./target/broken_links_checker.json - - uses: gaurav-nelson/github-action-markdown-link-check@v1 + - uses: tcort/github-action-markdown-link-check@v1 id: run-link-check with: { use-quiet-mode: yes, @@ -133,23 +141,28 @@ jobs: steps: - name: Checkout the repository id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: { fetch-depth: 0 } - name: Set up JDK 17 id: setup-java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: { distribution: temurin, java-version: '17', - cache: maven + cache: maven, + server-id: ossindex, + server-username: OSSINDEX_USERNAME, + server-password: OSSINDEX_API_TOKEN } - - { - name: Run tests and build with Maven 17, - id: build-next-java, + - name: Run tests and build with Maven 17 + id: build-next-java run: mvn --batch-mode clean package -DtrimStackTrace=false -Djava.version=17 - } + env: { + OSSINDEX_USERNAME: '${{ secrets.OSSINDEX_USERNAME }}', + OSSINDEX_API_TOKEN: '${{ secrets.OSSINDEX_API_TOKEN }}' + } build: needs: [ build-and-test, @@ -170,13 +183,13 @@ jobs: steps: - name: Checkout the repository id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: { fetch-depth: 0 } - name: Set up JDKs id: setup-java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: |- @@ -185,7 +198,7 @@ jobs: cache: maven - name: Check if release is needed id: check-release - if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') }} run: | if mvn --batch-mode com.exasol:project-keeper-maven-plugin:verify-release --projects .; then echo "### ✅ Release preconditions met, start release" >> "$GITHUB_STEP_SUMMARY" @@ -200,7 +213,7 @@ jobs: } start_release: needs: build - if: ${{ github.ref == 'refs/heads/main' && needs.build.outputs.release-required == 'true' }} + if: ${{ needs.build.outputs.release-required == 'true' }} concurrency: { cancel-in-progress: false, group: release @@ -213,5 +226,6 @@ jobs: } uses: ./.github/workflows/release.yml with: { - started-from-ci: true + started-from-ci: true, + maven-central-auto-publish: true } diff --git a/.github/workflows/dependencies_check.yml b/.github/workflows/dependencies_check.yml index 02c5aa0..b0877fc 100644 --- a/.github/workflows/dependencies_check.yml +++ b/.github/workflows/dependencies_check.yml @@ -18,7 +18,7 @@ jobs: issues: write } outputs: { - created-issues: '${{ steps.security-issues.outputs.created-issues }}' + created-issues: '${{ steps.create-security-issues.outputs.created-issues }}' } concurrency: { group: '${{ github.workflow }}-report_security_issues', @@ -28,17 +28,20 @@ jobs: - { name: Checkout, id: checkout, - uses: actions/checkout@v4 + uses: actions/checkout@v5 } - name: Set up JDKs id: setup-jdks - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: |- 11 17 cache: maven + server-id: ossindex + server-username: OSSINDEX_USERNAME + server-password: OSSINDEX_API_TOKEN - name: Generate ossindex report id: ossindex-report run: | @@ -46,9 +49,13 @@ jobs: org.sonatype.ossindex.maven:ossindex-maven-plugin:audit-aggregate \ -Dossindex.reportFile=$(pwd)/ossindex-report.json \ -Dossindex.fail=false - - name: Report Security Issues - id: security-issues - uses: exasol/python-toolbox/.github/actions/security-issues@main + env: { + OSSINDEX_USERNAME: '${{ secrets.OSSINDEX_USERNAME }}', + OSSINDEX_API_TOKEN: '${{ secrets.OSSINDEX_API_TOKEN }}' + } + - name: Create GitHub Issues + id: create-security-issues + uses: exasol/python-toolbox/.github/actions/security-issues@1.9.0 with: { format: maven, command: cat ossindex-report.json, @@ -60,7 +67,7 @@ jobs: echo "$CREATED_ISSUES" > test.jsonl cat test.jsonl env: { - CREATED_ISSUES: '${{ steps.security-issues.outputs.created-issues }}' + CREATED_ISSUES: '${{ steps.create-security-issues.outputs.created-issues }}' } start_dependency_udpate: needs: report_security_issues diff --git a/.github/workflows/dependencies_update.yml b/.github/workflows/dependencies_update.yml index c901506..2ba2785 100644 --- a/.github/workflows/dependencies_update.yml +++ b/.github/workflows/dependencies_update.yml @@ -8,6 +8,15 @@ on: required: true, type: string } + secrets: + INTEGRATION_TEAM_SLACK_NOTIFICATION_WEBHOOK: { + description: Slack webhook URL for notifications about failed builds., + required: true + } + INTEGRATION_TEAM_SECURITY_UPDATES_WEBHOOK: { + description: Slack webhook URL for notifications about new Pull Requests., + required: true + } workflow_dispatch: null jobs: update_dependencies: @@ -25,14 +34,14 @@ jobs: cancel-in-progress: false } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 id: checkout with: { fetch-depth: 0 } - name: Set up JDKs id: setup-jdks - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: |- @@ -49,7 +58,7 @@ jobs: - name: Fail if not running on a branch id: check-branch if: ${{ !startsWith(github.ref, 'refs/heads/') }} - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | core.setFailed('Not running on a branch, github.ref is ${{ github.ref }}. Please start this workflow only on main or a branch') @@ -172,5 +181,5 @@ jobs: message_format: '{workflow} created Pull Request ${{ steps.create-pr.outputs.pr_url }}' } env: { - SLACK_WEBHOOK_URL: '${{ secrets.INTEGRATION_TEAM_SLACK_NOTIFICATION_WEBHOOK }}' + SLACK_WEBHOOK_URL: '${{ secrets.INTEGRATION_TEAM_SECURITY_UPDATES_WEBHOOK }}' } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4682a3..dbc1ea2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,33 @@ on: required: true, default: false } + maven-central-auto-publish: { + description: 'Automatically publish to Maven Central. Deactivate to manually publish at https://central.sonatype.com/publishing/deployments', + required: true, + type: boolean, + default: true + } + secrets: + OSSRH_GPG_SECRET_KEY: { + description: Base64 encoded GPG secret key for signing artifacts for deployment to Maven Central., + required: false + } + OSSRH_GPG_SECRET_KEY_PASSWORD: { + description: 'Password for the GPG key. Must not contain special characters, only letters and numbers.', + required: false + } + MAVEN_CENTRAL_PORTAL_USERNAME: { + description: Username for the Maven Central Portal., + required: false + } + MAVEN_CENTRAL_PORTAL_TOKEN: { + description: Password for the Maven Central Portal., + required: false + } + INTEGRATION_TEAM_SLACK_NOTIFICATION_WEBHOOK: { + description: Slack webhook URL for notifications about failed and succesful releases., + required: true + } workflow_dispatch: inputs: skip-maven-central: { @@ -17,6 +44,12 @@ on: type: boolean, default: false } + maven-central-auto-publish: { + description: 'Automatically publish to Maven Central. Deactivate to manually publish at https://central.sonatype.com/publishing/deployments', + required: true, + type: boolean, + default: true + } skip-github-release: { description: Skip creating the GitHub release, required: true, @@ -42,21 +75,21 @@ jobs: steps: - name: Checkout the repository id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: { fetch-depth: 0 } - name: Set up Maven Central Repository id: configure-maven-central-credentials if: ${{ true }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: |- 11 17 cache: maven - server-id: ossrh + server-id: maven-central-portal server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }} @@ -64,27 +97,28 @@ jobs: - name: Set up JDKs id: setup-jdks if: ${{ ! true }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: |- 11 17 cache: maven - - name: Fail if not running on main branch - id: check-main-branch - if: ${{ github.ref != 'refs/heads/main' }} - uses: actions/github-script@v7 + - name: Fail if not running on main or release branch + id: check-main-or-release-branch + if: ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }} + uses: actions/github-script@v8 with: script: | - core.setFailed('Not running on main branch, github.ref is ${{ github.ref }}. Please start this workflow only on main') + core.setFailed('Not running on main or release branch, github.ref is ${{ github.ref }}. Please start this workflow only on main or a branch starting with release/') - name: Check CI build of this commit succeeded id: check-ci-build-status if: ${{ ! inputs.started-from-ci }} run: | echo "Commit SHA: $COMMIT_SHA" - gh run list --workflow ci-build.yml --branch main --event push --commit $COMMIT_SHA - ci_build_status=$(gh run list --workflow ci-build.yml --branch main --event push --commit $COMMIT_SHA --json conclusion --template '{{range .}}{{.conclusion}}{{"\n"}}{{end}}') + echo "Branch: $BRANCH_NAME" + gh run list --workflow ci-build.yml --branch $BRANCH_NAME --event push --commit $COMMIT_SHA + ci_build_status=$(gh run list --workflow ci-build.yml --branch $BRANCH_NAME --event push --commit $COMMIT_SHA --json conclusion --template '{{range .}}{{.conclusion}}{{"\n"}}{{end}}') echo "CI build status at commit $COMMIT_SHA was '$ci_build_status'" if [[ "$ci_build_status" != "success" ]]; then gh run list --workflow ci-build.yml --commit $COMMIT_SHA >> $GITHUB_STEP_SUMMARY @@ -94,7 +128,8 @@ jobs: fi env: { COMMIT_SHA: '${{ github.sha }}', - GH_TOKEN: '${{ github.token }}' + GH_TOKEN: '${{ github.token }}', + BRANCH_NAME: '${{ github.ref_name }}' } - name: Verify release preconditions id: verify-release @@ -107,7 +142,7 @@ jobs: - { name: Build project, id: build, - run: mvn --batch-mode -DskipTests clean verify + run: mvn --batch-mode -DskipTests -Dossindex.skip=true clean verify } - { name: List secret GPG keys, @@ -120,11 +155,18 @@ jobs: if: ${{ true && (! inputs.skip-maven-central) }} run: | echo "#### Maven Central Release" >> "$GITHUB_STEP_SUMMARY" - mvn --batch-mode -Dgpg.skip=false -DskipTests deploy - echo "Published to Maven Central ✅" >> "$GITHUB_STEP_SUMMARY" + mvn --batch-mode -Dgpg.skip=false -DskipTests -Dossindex.skip=true deploy \ + -Dcentral-publishing.deploymentName="Auto release of repo ${{ github.repository }} using PK release.yml" \ + -Dcentral-publishing.autoPublish=${{ inputs.maven-central-auto-publish }} + if [[ "${{ inputs.maven-central-auto-publish }}" == "true" ]]; then + echo "Published to Maven Central ✅" >> "$GITHUB_STEP_SUMMARY" + else + echo "Uploaded to Maven Central ✅" >> "$GITHUB_STEP_SUMMARY" + echo "⚠️ Go to https://central.sonatype.com/publishing/deployments to publish the release ⚠️" >> "$GITHUB_STEP_SUMMARY" + fi env: { - MAVEN_USERNAME: '${{ secrets.OSSRH_USERNAME }}', - MAVEN_PASSWORD: '${{ secrets.OSSRH_PASSWORD }}', + MAVEN_USERNAME: '${{ secrets.MAVEN_CENTRAL_PORTAL_USERNAME }}', + MAVEN_PASSWORD: '${{ secrets.MAVEN_CENTRAL_PORTAL_TOKEN }}', MAVEN_GPG_PASSPHRASE: '${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}' } - name: Calculate Artifact Checksums @@ -166,7 +208,7 @@ jobs: echo "* \`$file\`" >> "$GITHUB_STEP_SUMMARY" done echo "" >> "$GITHUB_STEP_SUMMARY" - release_url=$(gh release create --latest --title "$TITLE" --notes "$NOTES" --target main $TAG "${artifacts_array[@]}") + release_url=$(gh release create --latest --title "$TITLE" --notes "$NOTES" --target $BRANCH_NAME $TAG "${artifacts_array[@]}") echo "Created release $TAG with title '$TITLE' at $release_url ✅" >> "$GITHUB_STEP_SUMMARY" echo "release-url=$release_url" >> "$GITHUB_OUTPUT" @@ -188,7 +230,8 @@ jobs: ADDITIONAL_TAGS: '${{ steps.verify-release.outputs.additional-release-tags }}', NOTES: '${{ steps.verify-release.outputs.release-notes }}', TITLE: '${{ steps.verify-release.outputs.release-title }}', - ARTIFACTS: '${{ steps.artifact-checksum.outputs.artifacts }}' + ARTIFACTS: '${{ steps.artifact-checksum.outputs.artifacts }}', + BRANCH_NAME: '${{ github.ref_name }}' } - name: Report failure Status to Slack channel id: report-failure-status-slack diff --git a/README.md b/README.md index bebd084..fe21557 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,12 @@ A Virtual Schema adapter is basically a [UDF](https://docs.exasol.com/database_c * [List of Virtual Schema dialects](https://github.com/exasol/virtual-schemas/blob/main/doc/user-guide/dialects.md) * [Changelog](doc/changes/changelog.md) -* [User Requirements](doc/user_requirements.md) * [Dependencies](dependencies.md) ## Information for Developers * [Virtual Schema API](doc/development/api/virtual_schema_api.md) -* [Software Design](doc/design.md) * [Capabilities list](doc/development/api/capabilities_list.md) -* [System Requirements](doc/system_requirements.md) -* [Design](doc/design.md) -* [Developer Guide](doc/development/developer_guide.md) ## Open Source Project Support diff --git a/dependencies.md b/dependencies.md index 5191af7..aa2296e 100644 --- a/dependencies.md +++ b/dependencies.md @@ -3,23 +3,22 @@ ## Compile Dependencies -| Dependency | License | -| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| [Jakarta JSON Processing API][0] | [Eclipse Public License 2.0][1]; [GNU General Public License, version 2 with the GNU Classpath Exception][2] | -| [Exasol Database fundamentals for Java][3] | [MIT License][4] | -| [Exasol UDF API for Java][5] | [MIT License][6] | -| [error-reporting-java][7] | [MIT License][8] | +| Dependency | License | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| [Jakarta JSON Processing API][0] | [Eclipse Public License 2.0][1]; [GNU General Public License, version 2 with the GNU Classpath Exception][2] | +| [Exasol UDF API for Java][3] | [MIT License][4] | +| [error-reporting-java][5] | [MIT License][6] | ## Test Dependencies | Dependency | License | | ------------------------------------------ | ---------------------------------------------- | -| [Hamcrest][9] | [BSD-3-Clause][10] | -| [JSONassert][11] | [The Apache Software License, Version 2.0][12] | -| [JUnit Jupiter (Aggregator)][13] | [Eclipse Public License v2.0][14] | -| [mockito-junit-jupiter][15] | [MIT][16] | -| [JUnit5 System Extensions][17] | [Eclipse Public License v2.0][18] | -| [EqualsVerifier \| release normal jar][19] | [Apache License, Version 2.0][20] | +| [Hamcrest][7] | [BSD License 3][8] | +| [JSONassert][9] | [The Apache Software License, Version 2.0][10] | +| [JUnit Jupiter (Aggregator)][11] | [Eclipse Public License v2.0][12] | +| [mockito-junit-jupiter][13] | [MIT][14] | +| [JUnit5 System Extensions][15] | [Eclipse Public License v2.0][16] | +| [EqualsVerifier \| release normal jar][17] | [Apache License, Version 2.0][18] | ## Runtime Dependencies @@ -31,84 +30,78 @@ | Dependency | License | | ------------------------------------------------------- | ------------------------------------------- | -| [Apache Maven Clean Plugin][21] | [Apache-2.0][20] | -| [Apache Maven Install Plugin][22] | [Apache-2.0][20] | -| [Apache Maven Resources Plugin][23] | [Apache-2.0][20] | -| [Apache Maven Site Plugin][24] | [Apache-2.0][20] | -| [SonarQube Scanner for Maven][25] | [GNU LGPL 3][26] | -| [Apache Maven Toolchains Plugin][27] | [Apache-2.0][20] | -| [Apache Maven Compiler Plugin][28] | [Apache-2.0][20] | -| [Apache Maven Enforcer Plugin][29] | [Apache-2.0][20] | -| [Maven Flatten Plugin][30] | [Apache Software Licenese][20] | -| [org.sonatype.ossindex.maven:ossindex-maven-plugin][31] | [ASL2][12] | -| [Maven Surefire Plugin][32] | [Apache-2.0][20] | -| [Versions Maven Plugin][33] | [Apache License, Version 2.0][20] | -| [duplicate-finder-maven-plugin Maven Mojo][34] | [Apache License 2.0][35] | -| [Apache Maven Artifact Plugin][36] | [Apache-2.0][20] | -| [Apache Maven Deploy Plugin][37] | [Apache-2.0][20] | -| [Apache Maven GPG Plugin][38] | [Apache-2.0][20] | -| [Apache Maven Source Plugin][39] | [Apache License, Version 2.0][20] | -| [Apache Maven Javadoc Plugin][40] | [Apache-2.0][20] | -| [Nexus Staging Maven Plugin][41] | [Eclipse Public License][42] | -| [JaCoCo :: Maven Plugin][43] | [EPL-2.0][44] | -| [Quality Summarizer Maven Plugin][45] | [MIT License][46] | -| [error-code-crawler-maven-plugin][47] | [MIT License][48] | -| [Git Commit Id Maven Plugin][49] | [GNU Lesser General Public License 3.0][50] | -| [Project Keeper Maven plugin][51] | [The MIT License][52] | -| [OpenFastTrace Maven Plugin][53] | [GNU General Public License v3.0][54] | +| [Apache Maven Clean Plugin][19] | [Apache-2.0][18] | +| [Apache Maven Install Plugin][20] | [Apache-2.0][18] | +| [Apache Maven Resources Plugin][21] | [Apache-2.0][18] | +| [Apache Maven Site Plugin][22] | [Apache-2.0][18] | +| [SonarQube Scanner for Maven][23] | [GNU LGPL 3][24] | +| [Apache Maven Toolchains Plugin][25] | [Apache-2.0][18] | +| [Apache Maven Compiler Plugin][26] | [Apache-2.0][18] | +| [Apache Maven Enforcer Plugin][27] | [Apache-2.0][18] | +| [Maven Flatten Plugin][28] | [Apache Software License][18] | +| [org.sonatype.ossindex.maven:ossindex-maven-plugin][29] | [ASL2][10] | +| [Maven Surefire Plugin][30] | [Apache-2.0][18] | +| [Versions Maven Plugin][31] | [Apache License, Version 2.0][18] | +| [duplicate-finder-maven-plugin Maven Mojo][32] | [Apache License 2.0][33] | +| [Apache Maven Artifact Plugin][34] | [Apache-2.0][18] | +| [Apache Maven Deploy Plugin][35] | [Apache-2.0][18] | +| [Apache Maven GPG Plugin][36] | [Apache-2.0][18] | +| [Apache Maven Source Plugin][37] | [Apache License, Version 2.0][18] | +| [Apache Maven Javadoc Plugin][38] | [Apache-2.0][18] | +| [Central Publishing Maven Plugin][39] | [The Apache License, Version 2.0][18] | +| [JaCoCo :: Maven Plugin][40] | [EPL-2.0][41] | +| [Quality Summarizer Maven Plugin][42] | [MIT License][43] | +| [error-code-crawler-maven-plugin][44] | [MIT License][45] | +| [Git Commit Id Maven Plugin][46] | [GNU Lesser General Public License 3.0][47] | +| [Project Keeper Maven plugin][48] | [The MIT License][49] | [0]: https://github.com/eclipse-ee4j/jsonp [1]: https://projects.eclipse.org/license/epl-2.0 [2]: https://projects.eclipse.org/license/secondary-gpl-2.0-cp -[3]: https://github.com/exasol/db-fundamentals-java/ -[4]: https://github.com/exasol/db-fundamentals-java/blob/main/LICENSE -[5]: https://github.com/exasol/udf-api-java/ -[6]: https://github.com/exasol/udf-api-java/blob/main/LICENSE -[7]: https://github.com/exasol/error-reporting-java/ -[8]: https://github.com/exasol/error-reporting-java/blob/main/LICENSE -[9]: http://hamcrest.org/JavaHamcrest/ -[10]: https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE -[11]: https://github.com/skyscreamer/JSONassert -[12]: http://www.apache.org/licenses/LICENSE-2.0.txt -[13]: https://junit.org/junit5/ -[14]: https://www.eclipse.org/legal/epl-v20.html -[15]: https://github.com/mockito/mockito -[16]: https://opensource.org/licenses/MIT -[17]: https://github.com/itsallcode/junit5-system-extensions -[18]: http://www.eclipse.org/legal/epl-v20.html -[19]: https://www.jqno.nl/equalsverifier -[20]: https://www.apache.org/licenses/LICENSE-2.0.txt -[21]: https://maven.apache.org/plugins/maven-clean-plugin/ -[22]: https://maven.apache.org/plugins/maven-install-plugin/ -[23]: https://maven.apache.org/plugins/maven-resources-plugin/ -[24]: https://maven.apache.org/plugins/maven-site-plugin/ -[25]: http://docs.sonarqube.org/display/PLUG/Plugin+Library/sonar-maven-plugin -[26]: http://www.gnu.org/licenses/lgpl.txt -[27]: https://maven.apache.org/plugins/maven-toolchains-plugin/ -[28]: https://maven.apache.org/plugins/maven-compiler-plugin/ -[29]: https://maven.apache.org/enforcer/maven-enforcer-plugin/ -[30]: https://www.mojohaus.org/flatten-maven-plugin/ -[31]: https://sonatype.github.io/ossindex-maven/maven-plugin/ -[32]: https://maven.apache.org/surefire/maven-surefire-plugin/ -[33]: https://www.mojohaus.org/versions/versions-maven-plugin/ -[34]: https://basepom.github.io/duplicate-finder-maven-plugin -[35]: http://www.apache.org/licenses/LICENSE-2.0.html -[36]: https://maven.apache.org/plugins/maven-artifact-plugin/ -[37]: https://maven.apache.org/plugins/maven-deploy-plugin/ -[38]: https://maven.apache.org/plugins/maven-gpg-plugin/ -[39]: https://maven.apache.org/plugins/maven-source-plugin/ -[40]: https://maven.apache.org/plugins/maven-javadoc-plugin/ -[41]: http://www.sonatype.com/public-parent/nexus-maven-plugins/nexus-staging/nexus-staging-maven-plugin/ -[42]: http://www.eclipse.org/legal/epl-v10.html -[43]: https://www.jacoco.org/jacoco/trunk/doc/maven.html -[44]: https://www.eclipse.org/legal/epl-2.0/ -[45]: https://github.com/exasol/quality-summarizer-maven-plugin/ -[46]: https://github.com/exasol/quality-summarizer-maven-plugin/blob/main/LICENSE -[47]: https://github.com/exasol/error-code-crawler-maven-plugin/ -[48]: https://github.com/exasol/error-code-crawler-maven-plugin/blob/main/LICENSE -[49]: https://github.com/git-commit-id/git-commit-id-maven-plugin -[50]: http://www.gnu.org/licenses/lgpl-3.0.txt -[51]: https://github.com/exasol/project-keeper/ -[52]: https://github.com/exasol/project-keeper/blob/main/LICENSE -[53]: https://github.com/itsallcode/openfasttrace-maven-plugin -[54]: https://www.gnu.org/licenses/gpl-3.0.html +[3]: https://github.com/exasol/udf-api-java/ +[4]: https://github.com/exasol/udf-api-java/blob/main/LICENSE +[5]: https://github.com/exasol/error-reporting-java/ +[6]: https://github.com/exasol/error-reporting-java/blob/main/LICENSE +[7]: http://hamcrest.org/JavaHamcrest/ +[8]: http://opensource.org/licenses/BSD-3-Clause +[9]: https://github.com/skyscreamer/JSONassert +[10]: http://www.apache.org/licenses/LICENSE-2.0.txt +[11]: https://junit.org/junit5/ +[12]: https://www.eclipse.org/legal/epl-v20.html +[13]: https://github.com/mockito/mockito +[14]: https://opensource.org/licenses/MIT +[15]: https://github.com/itsallcode/junit5-system-extensions +[16]: http://www.eclipse.org/legal/epl-v20.html +[17]: https://www.jqno.nl/equalsverifier +[18]: https://www.apache.org/licenses/LICENSE-2.0.txt +[19]: https://maven.apache.org/plugins/maven-clean-plugin/ +[20]: https://maven.apache.org/plugins/maven-install-plugin/ +[21]: https://maven.apache.org/plugins/maven-resources-plugin/ +[22]: https://maven.apache.org/plugins/maven-site-plugin/ +[23]: http://docs.sonarqube.org/display/PLUG/Plugin+Library/sonar-scanner-maven/sonar-maven-plugin +[24]: http://www.gnu.org/licenses/lgpl.txt +[25]: https://maven.apache.org/plugins/maven-toolchains-plugin/ +[26]: https://maven.apache.org/plugins/maven-compiler-plugin/ +[27]: https://maven.apache.org/enforcer/maven-enforcer-plugin/ +[28]: https://www.mojohaus.org/flatten-maven-plugin/ +[29]: https://sonatype.github.io/ossindex-maven/maven-plugin/ +[30]: https://maven.apache.org/surefire/maven-surefire-plugin/ +[31]: https://www.mojohaus.org/versions/versions-maven-plugin/ +[32]: https://basepom.github.io/duplicate-finder-maven-plugin +[33]: http://www.apache.org/licenses/LICENSE-2.0.html +[34]: https://maven.apache.org/plugins/maven-artifact-plugin/ +[35]: https://maven.apache.org/plugins/maven-deploy-plugin/ +[36]: https://maven.apache.org/plugins/maven-gpg-plugin/ +[37]: https://maven.apache.org/plugins/maven-source-plugin/ +[38]: https://maven.apache.org/plugins/maven-javadoc-plugin/ +[39]: https://central.sonatype.org +[40]: https://www.jacoco.org/jacoco/trunk/doc/maven.html +[41]: https://www.eclipse.org/legal/epl-2.0/ +[42]: https://github.com/exasol/quality-summarizer-maven-plugin/ +[43]: https://github.com/exasol/quality-summarizer-maven-plugin/blob/main/LICENSE +[44]: https://github.com/exasol/error-code-crawler-maven-plugin/ +[45]: https://github.com/exasol/error-code-crawler-maven-plugin/blob/main/LICENSE +[46]: https://github.com/git-commit-id/git-commit-id-maven-plugin +[47]: http://www.gnu.org/licenses/lgpl-3.0.txt +[48]: https://github.com/exasol/project-keeper/ +[49]: https://github.com/exasol/project-keeper/blob/main/LICENSE diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 2aafdd8..38b496c 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,6 +1,6 @@ # Changes -* [18.0.0](changes_18.0.0.md) +* [17.1.1](changes_17.1.1.md) * [17.1.0](changes_17.1.0.md) * [17.0.1](changes_17.0.1.md) * [17.0.0](changes_17.0.0.md) diff --git a/doc/changes/changes_17.1.0.md b/doc/changes/changes_17.1.0.md index cc27845..cbdbdcd 100644 --- a/doc/changes/changes_17.1.0.md +++ b/doc/changes/changes_17.1.0.md @@ -10,7 +10,7 @@ This release adds support for scalar function [`WIDTH_BUCKET`](https://docs.exas The release also adds support for `TIMESTAMP` precision. -**Breaking Changes:** This release removes the following deprecated fields/methods from class `com.exasol.adapter.properties.AdapterProperties`: +**Breaking Changes:** This release removes the following deprecated fields/methods from class `com.exasol.adapter.AdapterProperties`: * Constant `EXCEPTION_HANDLING_PROPERTY` * Method `getExceptionHandling()` * Method `hasExceptionHandling()` diff --git a/doc/changes/changes_17.1.1.md b/doc/changes/changes_17.1.1.md new file mode 100644 index 0000000..cc3b55d --- /dev/null +++ b/doc/changes/changes_17.1.1.md @@ -0,0 +1,38 @@ +# Common Module of Exasol Virtual Schemas Adapters 17.1.1, released 2025-11-13 + +Code name: Fixing the main branch, updating PK + +## Summary + +This is a technical release needed to fix the main branch consistency. + +## Features + +* #293: The main branch is broken + +## Dependency Updates + +### Plugin Dependency Updates + +* Updated `com.exasol:error-code-crawler-maven-plugin:2.0.2` to `2.0.5` +* Updated `com.exasol:project-keeper-maven-plugin:4.3.0` to `5.4.3` +* Added `com.exasol:quality-summarizer-maven-plugin:0.2.1` +* Added `io.github.git-commit-id:git-commit-id-maven-plugin:9.0.2` +* Removed `io.github.zlika:reproducible-build-maven-plugin:0.16` +* Added `org.apache.maven.plugins:maven-artifact-plugin:3.6.1` +* Updated `org.apache.maven.plugins:maven-clean-plugin:3.2.0` to `3.5.0` +* Updated `org.apache.maven.plugins:maven-compiler-plugin:3.13.0` to `3.14.1` +* Updated `org.apache.maven.plugins:maven-deploy-plugin:3.1.1` to `3.1.4` +* Updated `org.apache.maven.plugins:maven-enforcer-plugin:3.4.1` to `3.6.2` +* Updated `org.apache.maven.plugins:maven-gpg-plugin:3.2.2` to `3.2.8` +* Updated `org.apache.maven.plugins:maven-install-plugin:3.1.2` to `3.1.4` +* Updated `org.apache.maven.plugins:maven-javadoc-plugin:3.6.3` to `3.12.0` +* Updated `org.apache.maven.plugins:maven-site-plugin:3.12.1` to `3.21.0` +* Updated `org.apache.maven.plugins:maven-surefire-plugin:3.2.5` to `3.5.4` +* Updated `org.apache.maven.plugins:maven-toolchains-plugin:3.1.0` to `3.2.0` +* Updated `org.codehaus.mojo:flatten-maven-plugin:1.6.0` to `1.7.3` +* Updated `org.codehaus.mojo:versions-maven-plugin:2.16.2` to `2.19.1` +* Updated `org.jacoco:jacoco-maven-plugin:0.8.12` to `0.8.14` +* Updated `org.sonarsource.scanner.maven:sonar-maven-plugin:3.11.0.3922` to `5.2.0.4988` +* Added `org.sonatype.central:central-publishing-maven-plugin:0.9.0` +* Removed `org.sonatype.plugins:nexus-staging-maven-plugin:1.6.13` diff --git a/doc/changes/changes_18.0.0.md b/doc/changes/changes_18.0.0.md deleted file mode 100644 index a545039..0000000 --- a/doc/changes/changes_18.0.0.md +++ /dev/null @@ -1,56 +0,0 @@ -# Common Module of Exasol Virtual Schemas Adapters 18.0.0, released 2025-04-?? - -Code name: Property Validation Rework - -## Summary - -In this release we reworked the Virtual Schema property validation infrastructure. Many parts were in VS Common JDBC, although they are relevant for virtual schemas. - -* Moved all virtual schema property related classes to new package `com.exasol.adpater.properties` to make locating them easier. -* Added property validators -* Added system requirements and user guide -* Added developer guide - -## Features - -* #284: New property validation framework -* #287: Multi-select and Unix path validator - -## Dependency Updates - -### Compile Dependency Updates - -* Added `com.exasol:db-fundamentals-java:0.1.3` - -### Test Dependency Updates - -* Updated `nl.jqno.equalsverifier:equalsverifier:3.16.1` to `3.19.1` -* Updated `org.hamcrest:hamcrest:2.2` to `3.0` -* Updated `org.itsallcode:junit5-system-extensions:1.2.0` to `1.2.2` -* Updated `org.junit.jupiter:junit-jupiter:5.10.2` to `5.12.0` -* Updated `org.mockito:mockito-junit-jupiter:5.11.0` to `5.16.0` -* Updated `org.skyscreamer:jsonassert:1.5.1` to `1.5.3` - -### Plugin Dependency Updates - -* Updated `com.exasol:error-code-crawler-maven-plugin:2.0.2` to `2.0.3` -* Updated `com.exasol:project-keeper-maven-plugin:4.3.0` to `5.0.1` -* Added `com.exasol:quality-summarizer-maven-plugin:0.2.0` -* Added `io.github.git-commit-id:git-commit-id-maven-plugin:9.0.1` -* Removed `io.github.zlika:reproducible-build-maven-plugin:0.16` -* Added `org.apache.maven.plugins:maven-artifact-plugin:3.6.0` -* Updated `org.apache.maven.plugins:maven-clean-plugin:3.2.0` to `3.4.1` -* Updated `org.apache.maven.plugins:maven-compiler-plugin:3.13.0` to `3.14.0` -* Updated `org.apache.maven.plugins:maven-deploy-plugin:3.1.1` to `3.1.4` -* Updated `org.apache.maven.plugins:maven-enforcer-plugin:3.4.1` to `3.5.0` -* Updated `org.apache.maven.plugins:maven-gpg-plugin:3.2.2` to `3.2.7` -* Updated `org.apache.maven.plugins:maven-install-plugin:3.1.2` to `3.1.4` -* Updated `org.apache.maven.plugins:maven-javadoc-plugin:3.6.3` to `3.11.2` -* Updated `org.apache.maven.plugins:maven-site-plugin:3.12.1` to `3.21.0` -* Updated `org.apache.maven.plugins:maven-surefire-plugin:3.2.5` to `3.5.2` -* Updated `org.apache.maven.plugins:maven-toolchains-plugin:3.1.0` to `3.2.0` -* Updated `org.codehaus.mojo:flatten-maven-plugin:1.6.0` to `1.7.0` -* Updated `org.codehaus.mojo:versions-maven-plugin:2.16.2` to `2.18.0` -* Added `org.itsallcode:openfasttrace-maven-plugin:2.3.0` -* Updated `org.sonarsource.scanner.maven:sonar-maven-plugin:3.11.0.3922` to `5.0.0.4389` -* Updated `org.sonatype.plugins:nexus-staging-maven-plugin:1.6.13` to `1.7.0` \ No newline at end of file diff --git a/doc/design.md b/doc/design.md deleted file mode 100644 index e6a45a8..0000000 --- a/doc/design.md +++ /dev/null @@ -1,327 +0,0 @@ -## Concepts - -### BucketFS - - -To understand virtual schema adapters, it is essential to first become familiar with the concept of BucketFS. - -**BucketFS** is a Unix-based file system that automatically distributes files across the worker nodes of an Exasol cluster. Users can interact with BucketFS via an HTTP interface, which supports uploading, downloading, listing, and deleting files. - -In essence, virtual schemas are User-Defined Functions (UDFs). The only part of the node filesystem a UDF can access is a BucketFS bucket. To improve performance, UDFs interact directly with the local filesystem that backs the buckets, avoiding the overhead of HTTP communication. - -Buckets operate within a _chroot_ environment, which is a security measure that prevents UDFs from accessing unauthorized files. This ensures that UDFs remain confined to their designated paths. - -All paths in BucketFS begin with: -`/buckets//` - -In the context of virtual schemas, buckets are typically used to: - -1. Store the libraries that implement virtual schema adapters. -2. Hold drivers required for accessing data sources. -3. Host configuration files, TLS certificates, and key files. - -While there are other potential use cases for buckets, the examples above are the most common. - -For more detailed information, refer to the dedicated ["BucketFS"](https://docs.exasol.com/db/latest/database_concepts/bucketfs/bucketfs.htm?Highlight=bucketfs) section in the Exasol handbook. - - -## Building Block View - -### Property Validator Building Blocks - -The UML diagram below shows the relationship between the factory for the validators, the abstract base class and a concrete validator. - -```plantuml -hide empty members - -interface PropertyValidator { - * validate() : PropertyValidationResult - * getPropertyName() : String - * getErrorCode() : String -} - -class AbstractPropertyValidator <> { - ~ AbstractPropertyValidator(context PropertyValidationContext context, propertyName : String, errorCode: String) - # createError() : void - * validate() : PropertyValidationResult - # performSpecificValidation : PropertyValidationResult <> -} - -AbstractPropertyValidator -u-|> PropertyValidator -ValidatorFactory -d-> ValidationContext -ValidatorFactory -u-> BooleanValidator -ValidatorFactory -u-> IntegerValidator -ValidatorFactory -u-> StringValidator -ValidationContext --> AdapterProperties -ValidationContext --> ValidationLog -BooleanValidator -u-|> AbstractPropertyValidator -IntegerValidator -u-|> AbstractPropertyValidator -StringValidator -u-|> AbstractPropertyValidator -``` - -Please note that the Validator constructors are all **package-private**. User can intentionally only create validators through the `PropertyValidatorFactory`. - -## Runtime View - -### Property Validation - -We want to avoid that users are plagued with subtle error caused by typos in virtual schema property names or values. - -Dedicated validators check everything from the correctness of the name spelling to only using allowed values. - -This also serves as a first line of defense against injecting malicious parameters through user input. - -There are a couple of rules about how virtual schemas handle properties: - -1. The core database hands properties to the virtual schema adapter at the beginning of the request. -2. Property names and values are strings when passed by the database. -3. They are passed "as-is", just like the users entered them. -4. The core database does **not validate** virtual schema properties. That is the job of the adapter. -5. During a request the virtual schema properties don't change. That means we treat them as immutable in the context of a request. -6. Properties can only change in `CREATE` and `REFRESH` requests. - -#### Validating Boolean Properties -`dsn~validating-boolean-properties~1` - -The `BooleanValidator` checks whether the value or a property is either `true` or `false`, ignoring the case. - -Covers: - -* `req~validating-boolean-properties~1` - -Needs: impl, utest - -#### Validating Integer Properties -`dsn~validating-integer-properties~1` - -The `IntegerValidator` checks whether the value of a property is a valid integer number. - -Covers: - -* `req~validating-integer-properties~1` - -Needs: impl, utest - -#### Integer Interval Validation -`dsn~integer-interval-validation~1` - -The `IntegerValidator` allows adding optional upper and lower boundary between which the checked number must be. - -Covers: - -* `req~validating-integer-properties~1` - -Needs: impl, utest - -#### Validating Exasol Object ID Properties -`dsn~validating-exasol-object-id-properties~1` - -The `ExasolObjectIdValidator` checks whether a given String is a valid Exasol Database Object ID. - -The rule of the object IDs follow the [official Exasol documentation](https://docs.exasol.com/db/latest/sql_references/basiclanguageelements.htm#SQLidentifier) (as of 2025-04-08). - -We use the library [`db-fundamentals-java`](https://github.com/exasol/db-fundamentals-java) for the validation. - -Covers: - -* `req~validating-properties-containing-database-objects-ids~1` - -Needs: impl, utest - -#### Validating Enumeration Properties -`dsn~validating-enumeration-properties~1` - -The `EnumerationValiator` checks that a given value is one of the values of an enumeration. - -Covers: - -* `req~validating-enumeration-properties~1` - -Needs: impl, utest - -#### Validating Multi-Select Properties -`dsn~validating-multi-select-properties~1` - -The `MultiSelectValidator` checks that a given comma-separated list contains only values from a given enumeration. - -Covers: - -* `req~validating-multi-select-properties~1` - -Needs: impl, utest - -#### Validating the Existence of Mandatory Properties -`dsn~validating-the-existence-of-mandatory-properties~1` - -The `RequiredValidator` validates that a mandatory property - -1. Exists -2. Is not null -3. Is not empty - -Covers: - -* `req~validating-the-existence-of-mandatory-properties~1` - -Needs: impl, utest - -#### Validating a String Against a Regular Expression -`dsn~validating-a-string-against-a-regular-expression~1` - -The `StringValidator` checks whether a given string value matches a regular expression. - -Covers: - -* `req~validating-a-string-against-a-regular-expression~1` - -Needs: impl, utest - -#### Reporting Format Violations in Properties -`dsn~reporting-format-violations-in-properties~1` - -If the user specifies a format description, the `StringValdiator` uses that in an error message instead of the regular expression. - -Rationale: - -For the end user it is much easier to understand what the validator expects when it requires something like ":" instead of a lengthy regular expression. - -Covers: - -* `req~validating-a-string-against-a-regular-expression~1` - -Needs: impl, utest - -#### Validating Unix Paths -`dsn~validating-unix-paths~1` - -The `UnixPathValiator` validates that a property contains a valid Unix path. - -Comment: - -See section ["BucketFS"](#bucketfs) for reasons why we need a Unix path validator. - -Covers: - -* `req~validating-unix-paths~1` - -Needs: impl, utest - -#### Only Absolute Paths are Valid in Properties -`dsn~only-absolute-paths-are-valid-in-properties~1` - -The `UnixPathValidator` allows only absolute paths. - -Covers: - -* `req~validator-allows-only-absolute-paths~1` - -Needs: impl, utest - -#### Short-circuiting "And" Validation -`dsn~short-circuiting-and-validation~1` - -The `AndValidator` executes sub-validators in a pre-defined order. It stops the validation on the first failed sub-validation, returning that result. - -Covers: - -* `req~validating-combinations-of-properties~1` - -Needs: impl, utest - -#### "All-of" Validation -`dsn~all-of-validation~1` - -The `AllOfValidator` check executes all sub-validators in a pre-defined order. It collects all failures and returns the combined result. - -Covers: - -* `req~validating-combinations-of-properties~1` - -Needs: impl, utest - -#### Validation Completeness Check -`dsn~validation-completeness-check~1` - -The `CoverageValidator` ensures that all configurable properties, whether mandatory or optional, are properly validated. - -Covers: - -*`req~property-validation-completeness~1` -*`req~validating-the-existence-of-mandatory-properties~1` -*`req~validating-that-an-optional-property-is-allowed~1` - -Needs: impl, utest - -### Validating the Absence of Unwanted Properties -`dsn~validating-the-absence-of-unwanted-properties~1` - -The `CoverageValidator` ensures that no unwanted property is set. - -Covers: - -* `req~validating-the-absence-of-unwanted-properties~1` - -Needs: impl, utest - -## Design Decisions - -### How do we Define Virtual Schema Property Validations? - -There are a couple of options on how to define property validations. Ranging from cascades of simple conditionals to an elaborate DSL. - -The decision is architecture relevant since it impacts: - -* system complexity -* reliability -* maintainability - -This project is a base library for multiple virtual schemas. Once the decision had been made, the effects propagate into the dependent VS. - -Things would be easy if each property only needed to be validated in isolation. Unfortunately however, this is not the case. Some properties depend on each other, so some logic is required. - -Additionally, some properties are shared by multiple VS. Take the log settings for example. They are relevant for all VS and that is why you find them in this base library here. - -That means that a base library should support validating the properties it manages and higher level modules should add their own support. - -The worst possible situation would be where a higher level module needs to change the validation of a property that is managed by a library below. - -We considered the following alternatives: - -* Internal DSL - - In this scenario developers can define validations and add to the list of validations in dependent modules. - - This is an elegant solution, that produces nice looking code, but adds considerable complexity. It allows for preventing code duplication - -* Validator chaining - - A simpler variant than the DSL, but less flexible, this allows running through a chain of validators. While this also prevents duplication, it does not handle dependencies between the validated properties out of the box. - -* Validator functions combined with regular condition cascades - - This is a very flexible solution, that also produces very readable code. The main downside is that it would offload the responsibility for calling all necessary validations to the VS dialect. That also means there will be code duplication, not only on the implementation side (which would not be a lot), but also in unit tests. - -### Validator Composition -`dsn~validator-composition~1` - -The `PropertyValdiatorFactory` allows creating compositions of base validators for virtual schema properties. - -Rationale: - -While being simple enough the composition approach allows for enough flexibility. - -Rationale: - -This allows providing and testing base property validation in the lower layer libraries and only adding what is needed in the dialects. The main benefit is that coupling is kept to a minimum, so that if a validation needs to be improved in a base library, the improvement will propagate to the dialects without code changes there. - -Covers: - -* `req~validating-combinations-of-properties~1` - -Needs: impl, utest - -## Test Strategy - -### Property Validation Tests - -The constructors of the property validators are intentionally package-private. Users can only generate validators through the `PropertyValidationFactory`. This also means that the unit test of each validator must use the factory. An added benefit of this approach is that the test coverage for the factory is automatically given when a validator is tested. \ No newline at end of file diff --git a/doc/development/developer_guide.md b/doc/development/developer_guide.md deleted file mode 100644 index 410d610..0000000 --- a/doc/development/developer_guide.md +++ /dev/null @@ -1,91 +0,0 @@ -# Developer Guide — Virtual Schema Common Java - -## Adapter Property Validation - -Virtual schema adapter properties are user input and must be validated before being used. On the one hand to protect users from typos and misunderstandings and on the other to secure the adapter and prevent injection of malicious parameters. - -### Validators - -This library offers several validators that make checking the adapter properties easy. - -In a typical scenario you have some base properties that all adapters know. This library for example understands the `LOG_LEVEL` property that lets users change the log level. - -Virtual Schema dialects (like the one for MariaDB) offer additional properties. That is why the validation is controlled by higher level modules in the dialect. - -Let's look at the simplest possible Validator first, a validator for boolean values. - -```java -import com.exasol.adapter.properties.AdapterProperties; -import com.exasol.adapter.properties.PropertyValidator; -import com.exasol.adapter.properties.ValidationResult; -import com.exasol.adapter.properties.ValidatorFactory; - -import java.util.Map; - -final AdapterProperties adapterProperties = new AdapterProperties(Map.of("THE_PARAMETER", true)); - -final ValidatorFactory factory = ValidatorFactory.create(adapterProperties); -final PropertyValidator validator = factory.bool("THE_PARAMETER"); -final ValidationResult result = validator.validate(); -``` - -In a real adapter the properties would of course not be constructed by hand but rather be passed as part of handling an adapter request. For the sake of simplicity however, this example takes a shortcut. - -The `ValidatorFactory` is responsible for creating all validators. It also keeps track of which parameters are covered by validators, but we will get to that later. - -Each validator implements the interface `PropertyValidator`, where the most important method is `validate`. This method returns a validation result consisting of an indicator whether the validation succeeded and an error message if it did not. - -| Validator Class | Factory Method | Purpose | -|---------------------------|------------------------------------------------------|--------------------------------------------------------------------------------| -| `AndValidator` | `and(validator1, validator2, ...)` | Short-circuits validation on first failure | -| `BooleanValidator` | `bool(propertyName)` | Validates that a property value is a boolean | -| `AllOfValidator` | `allOf(validator1, validator2, ...)` | Runs all validators without short-circuiting | -| `CoverageValidator` | `allCovered()` | Validates that all properties have been checked by validators | -| `EnumerationValidator` | `enumeration(propertyName, enumClass)` | Validates that a property value is one of the values defined in an enumeration | -| `ExasolObjectIdValidator` | `exasolObjectId(propertyName)` | Validates that a property value follows Exasol's object ID format rules | -| `IntegerValidator` | `integer(propertyName)` | Validates that a property value is a valid integer | -| `IntegerValidator` | `integer(propertyName, min, max)` | Validates that a property value is an integer within specified boundaries | -| `MultiSelectValidator` | `multiSelect(propertyName, enumClass)` | Validates a given value is a comma separated list of enum values; non-empty | -| `MultiSelectValidator` | `multiSelect(propertyName, enumClass, emptyAllowed)` | Validates a given value is a comma separated list of enum values | -| `RequiredValidator` | `required(propertyName)` | Validates that a property exists | -| `StringValidator` | `matches(propertyName, pattern)` | Validates property against regex pattern | -| `UnixPathValidator` | `path(propertyName)` | Validates a given value is a valid path without dangerous contents | - -### Validator Composition - -To allow complex validation the factory has a couple of methods that allow creating compositions of validators. - -```java -factory.and( - factory.required("REQUIRED_PARAMETER"), - factory.bool("REQUIRED_PARAMETER") -) -``` - -This allows defining a parameter as required and a boolean. The `AndValidator` short-circuits on the first failure. That means, that if the parameter is not set, the boolean check will be skipped. - -For this particular combination, there is also a shorthand form: - -```java -factory.required(factory.bool("REQUIRED_PARAMETER")) -``` - -This is much more convenient. - -If short-circuiting is not an option — for example because you want to present all validation failures at once instead of forcing users in to a trial-and-error loop — use the `AllOfValidator`. - -```java -factory.allOf( - factory.matches("HOST", "\\w+"), - factory.integer("PORT") -) -``` - -We recommend also checking that no unwanted properties are set with the `CoverageValidator`. Put that one at the very end. - -```java -factory.allOf( - // … all your other validators - factory.allCovered() -) -``` \ No newline at end of file diff --git a/doc/system_requirements.md b/doc/system_requirements.md deleted file mode 100644 index 7df4dd9..0000000 --- a/doc/system_requirements.md +++ /dev/null @@ -1,285 +0,0 @@ -# System Requirement Specification (SRS) Virtual Schema Common Java (VSCJ) - -This document contains the system requirements for the Java bottom-layer base library for all Exasol Virtual Schema adapters. - -## Terms and Abbreviations - -###### Database Object ID - -A unique identifier used to reference specific objects in a database. - -###### Dialect - -A specific implementation or variation of features, behaviors, or rules for interacting with a given data source or database type in the context of Virtual Schemas. - -###### Virtual Schema - -A database schema that only exists as a projection of an external data source. The data in the virtual schema is not permanently stored on the Exasol database. - -###### Virtual Schema Property - -A configuration option that controls the structure or behavior of a [virtual schema](#virtual-schema). - -###### VS - -See [Virtual Schema](#virtual-schema) - -###### VSCJ - -The abbreviation for "Virtual Schema Common Java", the base library designed for all Java Exasol Virtual Schema adapters. - -###### UDF - -See [User-Defined Function](#user-defined-function-udf) - -###### User-Defined Function (UDF) - -A function written by the user, typically in a programming language such as Java or Python, that can be executed within the Exasol database. UDFs allow for custom computational logic to be integrated directly into database operations, providing an extension point for tailored data processing and manipulation. - -## Features - -### Property Validation -`feat~property-validation~1` - -The VSCJ library provides the infrastructure for validating the input coming from virtual schema properties. - -Rationale: - -The validation on the one hand provides users with better information about properties that have been set incorrectly and on the other hand improves the security by preventing faulty or malicious property values to endanger the VS operation. - -Needs: req - -## High-level Requirements - -### Property Validation - -Users provide properties with virtual schema definitions that serve as configuration. - -Here is a non-exhaustive list of typical properties that should give an idea of what they are used for: - -1. Name under which the virtual schema can be found in the Exasol database -2. ID of the connection object that stores the access data to the data source -3. Log level -4. Target host for logs -5. Connection type selector -6. Feature switch - -Please refer to [Appendix A — Known Virtual Schema Property Types](#appendix-a--known-virtual-schema-property-types) for a more complete list. - -Note that some of these properties have relationships that require to validate them together. In the Exasol Virtual Schema for instance, the selected connection type decides which other connection properties are required. - -Also, some of the validations are so [dialect-specific](#dialect), that covering them in the VSCJ base library is not reasonable. - -#### Validating the Existence of Mandatory Properties -`req~validating-the-existence-of-mandatory-properties~1` - -VSCJ allows validating that a mandatory virtual schema property is set. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -#### Validating the Absence of Unwanted Properties -`req~validating-the-absence-of-unwanted-properties~1` - -VSCJ allows validating that an unwanted property is not set. - -Rationale: - -This is for cases where the same dialect can do different things depending on the configuration. In such a scenario we want to make sure that users only provide the parameter that are actually evaluated. This prevents confusion on the user's part. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -#### Validating That an Optional Property Is Allowed -`req~validating-that-an-optional-property-is-allowed~1` - -VSCJ allows validating that a property the user provides is a valid optional property. - -Rationale: - -It is a subtle source of errors if users provide a property that is not wanted by the virtual schema. Ignoring it is not a good option, since that has the potential to confuse the users. Imagine a situation where they misspell the property name. An error message would immediately tell them that this is not the property name they intended to provide. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -#### Validating Boolean Properties -`req~validating-boolean-properties~1` - -VSCJ checks whether the value of a boolean property is `true` or `false`. - -Rationale: - -Boolean properties are often used as feature switches. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -#### Validating Integer Properties -`req~validating-integer-properties~1` - -VSCJ checks whether a property is - -- a proper integer value -- within the allowed interval - -Rationale: - -This is useful for configurations like a port range for example. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validating Properties Containing Database Object IDS -`req~validating-properties-containing-database-objects-ids~1` - -VSCJ validates that a property referencing a database object contains a valid Exasol database object ID. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validating Enumeration Properties -`req~validating-enumeration-properties~1` - -VSCJ validates that the value given by the user is one of the values in an enumeration. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validating Multi-Select Properties - -`req~validating-multi-select-properties~1` - -VSCJ validates that the values provided are a valid subset of a predefined multi-select enumeration. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validating a String Against a Regular Expression -`req~validating-a-string-against-a-regular-expression~1` - -VSCJ validates string values against a regular expression. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validating Unix Paths -`req~validating-unix-paths~1` - -VSCJ validates that the value provided is a valid Unix part. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validator Allows Only Absolute Paths -`req~validator-allows-only-absolute-paths~1` - -The Property validator only allows absolute paths. - -Rationale: - -At the time this requirement was written (2025-04-23), there is only one known use case for a UnixPath in a property and that is pointing a mapping file for document-based virtual schemas. If necessary, this restriction can be loosened at a later point. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Validating Combinations of Properties -`req~validating-combinations-of-properties~1` - -VSCJ allows validating combinations of properties where property values can depend on others. - -Rationale: - -Consider a virtual schema dialect for a source like MariaDB. You have shared properties like log settings on the base layer, JDBC settings in the JDBC layer and finally dialect specifics like handling of the identifier case. The dialect will only function properly if all properties are set correctly. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Property Validation Completeness -`req~property-validation-completeness~1` - -VSCJ checks that all properties given by the virtual schema user are validated. - -Rationale: - -This helps to prevent typos, using outdated properties and properties from the wrong dialect. - -Covers: - -* [`feat~property-validation~1`](#property-validation) - -Needs: dsn - -### Known Limitations - -* For now, we limit the property validation to the value syntax and ranges. -* During value validation, we do not check if the referenced database objects actually exist. - -## Appendix A — Known Virtual Schema Property Types - -To make sure we don't forget requirements, here is a list of all known Virtual Schema properties as of 2025-03-13 and their types. - -Please note that the list will be outdated at some point. Its main purpose is to get a good sample of all property variants. - -| Property Name | Virtual Schema Dialect | Type | O/M | -|---------------------------------------------------------|------------------------------|---------------------------------------|-----| -| CATALOG_NAME | Multiple dialects | Database object ID (dialect-specific) | O | -| CONNECTION_NAME | All | Exasol database object ID | M | -| DEBUG_ADDRESS | All | <host>:<port> | O | -| DEBUG_LEVEL | All | Enumeration | O | -| EXA_CONNECTION_NAME | Exasol | Exasol database object ID | O | -| EXCLUDED_CAPABILITIES | All | Multi-select enum, comma-separated | O | -| GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA | Exasol | Boolean | O | -| GENERATE_JDBC_DATATYPE_MAPPING_FOR_OCI | Oracle | Boolean | O | -| IGNORE_ERRORS | All (for debugging purposes) | Boolean | O | -| IMPORT_FROM_EXA | Exasol | Boolean | O | -| IMPORT_FROM_ORA | Oracle | Boolean | O | -| IS_LOCAL | Exasol | Boolean | O | -| MAPPING | Azure Blob Storage | Unix file path | M | -| MAX_PARALLEL_UDFS | Azure Blob Storage | Integer | O | -| ORA_CONNECTION_NAME | Oracle | Exasol database object ID | O | -| ORACLE_CAST_NUMBER_TO_DECIMAL_WITH_PRECISION_AND_SCALE | Oracle | <Integer>,<Integer> | O | -| POSTGRESQL_IDENTIFIER_MAPPING | PostgreSQL | Enum | O | -| TABLE_FILTER | All (optional filtering) | Comma-separated list of object IDs | O | -| SCHEMA_NAME | Multiple dialects | Database object ID (dialect-specific) | M | - -You can see from the list that some property types are universal and others are highly dialect specific. And Exasol database object ID is only superficially similar to one of MySQL or PostgreSQL. - -Outdated properties, i.e. properties that don't play any role anymore or are already removed: - -* `DIALECT_NAME` — obsolete since now each dialect has its own JAR package \ No newline at end of file diff --git a/error_code_config.yml b/error_code_config.yml index 5cf7563..b147b36 100644 --- a/error_code_config.yml +++ b/error_code_config.yml @@ -2,4 +2,4 @@ error-tags: VSCOMJAVA: packages: - com.exasol - highest-index: 59 \ No newline at end of file + highest-index: 41 \ No newline at end of file diff --git a/pk_generated_parent.pom b/pk_generated_parent.pom index 36b88ab..478bdf6 100644 --- a/pk_generated_parent.pom +++ b/pk_generated_parent.pom @@ -3,7 +3,7 @@ 4.0.0 com.exasol virtual-schema-common-java-generated-parent - 18.0.0 + 17.1.1 pom UTF-8 @@ -14,17 +14,11 @@ https://sonarcloud.io true + false + false + validated + Manual deployment of repo virtual-schema-common-java - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - The MIT License (MIT) @@ -51,7 +45,7 @@ org.apache.maven.plugins maven-clean-plugin - 3.4.1 + 3.5.0 org.apache.maven.plugins @@ -71,7 +65,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - 5.0.0.4389 + 5.2.0.4988 org.apache.maven.plugins @@ -95,7 +89,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.14.1 ${java.version} ${java.version} @@ -109,7 +103,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.5.0 + 3.6.2 enforce-maven @@ -132,7 +126,7 @@ org.codehaus.mojo flatten-maven-plugin - 1.7.0 + 1.7.3 true oss @@ -158,6 +152,9 @@ org.sonatype.ossindex.maven ossindex-maven-plugin 3.2.0 + + ossindex + audit @@ -171,7 +168,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + 3.5.4 @@ -182,7 +179,7 @@ org.codehaus.mojo versions-maven-plugin - 2.18.0 + 2.19.1 display-updates @@ -237,7 +234,7 @@ org.apache.maven.plugins maven-artifact-plugin - 3.6.0 + 3.6.1 check-build-plan @@ -259,7 +256,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + 3.2.8 sign-artifacts @@ -279,9 +276,9 @@ org.apache.maven.plugins maven-source-plugin - + 3.2.1 @@ -295,7 +292,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.2 + 3.12.0 attach-javadocs @@ -315,30 +312,23 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 + true - true - ossrh - https://oss.sonatype.org/ - 15 - 30 + maven-central-portal + + ${central-publishing.autoPublish} + ${central-publishing.waitUntil} + ${central-publishing.deploymentName} + ${central-publishing.skipPublishing} - - - default-deploy - deploy - - deploy - - - org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.14 prepare-agent @@ -379,7 +369,7 @@ com.exasol quality-summarizer-maven-plugin - 0.2.0 + 0.2.1 summarize-metrics @@ -392,7 +382,7 @@ com.exasol error-code-crawler-maven-plugin - 2.0.3 + 2.0.5 verify @@ -405,7 +395,7 @@ io.github.git-commit-id git-commit-id-maven-plugin - 9.0.1 + 9.0.2 get-the-git-infos diff --git a/pom.xml b/pom.xml index 1063b9f..3a6e3f8 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 virtual-schema-common-java - 18.0.0 + 17.1.1 Common module of Exasol Virtual Schemas Adapters This is one of the modules of Virtual Schemas Adapters. The libraries provided by this project are the foundation of the adapter development, i.e. adapters must be implemented on top of them. @@ -13,11 +13,6 @@ jakarta.json-api 2.1.3 - - com.exasol - db-fundamentals-java - 0.1.3 - org.glassfish jakarta.json @@ -33,37 +28,37 @@ org.hamcrest hamcrest - 3.0 + 2.2 test org.skyscreamer jsonassert - 1.5.3 + 1.5.1 test org.junit.jupiter junit-jupiter - 5.12.0 + 5.10.2 test org.mockito mockito-junit-jupiter - 5.16.0 + 5.11.0 test org.itsallcode junit5-system-extensions - 1.2.2 + 1.2.0 test nl.jqno.equalsverifier equalsverifier - 3.19.1 + 3.16.1 test @@ -77,7 +72,7 @@ com.exasol project-keeper-maven-plugin - 5.0.1 + 5.4.3 @@ -86,26 +81,12 @@ - - org.itsallcode - openfasttrace-maven-plugin - 2.3.0 - - - trace-requirements - - trace - - verify - - - virtual-schema-common-java-generated-parent com.exasol - 18.0.0 + 17.1.1 pk_generated_parent.pom diff --git a/src/main/java/com/exasol/adapter/properties/AbstractAdapterProperties.java b/src/main/java/com/exasol/adapter/AbstractAdapterProperties.java similarity index 94% rename from src/main/java/com/exasol/adapter/properties/AbstractAdapterProperties.java rename to src/main/java/com/exasol/adapter/AbstractAdapterProperties.java index a57a8ac..55246fe 100644 --- a/src/main/java/com/exasol/adapter/properties/AbstractAdapterProperties.java +++ b/src/main/java/com/exasol/adapter/AbstractAdapterProperties.java @@ -1,4 +1,4 @@ -package com.exasol.adapter.properties; +package com.exasol.adapter; import java.util.*; @@ -16,7 +16,8 @@ public abstract class AbstractAdapterProperties { * * @param properties map of property keys and values */ - protected AbstractAdapterProperties(final Map properties) { + @java.lang.SuppressWarnings("java:S5993") + public AbstractAdapterProperties(final Map properties) { this.properties = properties; } diff --git a/src/main/java/com/exasol/adapter/properties/AdapterProperties.java b/src/main/java/com/exasol/adapter/AdapterProperties.java similarity index 99% rename from src/main/java/com/exasol/adapter/properties/AdapterProperties.java rename to src/main/java/com/exasol/adapter/AdapterProperties.java index ddf46e2..4bad7bb 100644 --- a/src/main/java/com/exasol/adapter/properties/AdapterProperties.java +++ b/src/main/java/com/exasol/adapter/AdapterProperties.java @@ -1,4 +1,4 @@ -package com.exasol.adapter.properties; +package com.exasol.adapter; import java.util.*; import java.util.stream.Collectors; diff --git a/src/main/java/com/exasol/adapter/properties/AbstractPropertyValidator.java b/src/main/java/com/exasol/adapter/properties/AbstractPropertyValidator.java deleted file mode 100644 index 44325bf..0000000 --- a/src/main/java/com/exasol/adapter/properties/AbstractPropertyValidator.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.exasol.adapter.properties; - -/** - * Base class for implementing property validators. - * - *

- * Provides common functionality for validating a specific property, including accessing the property value, defining - * error handling, and logging validation actions. - * - *

- * Subclasses must implement {@link #performSpecificValidation()} to define property-specific validation logic. - */ -public abstract class AbstractPropertyValidator implements PropertyValidator { - /** The context for the validation with the adapter properties and a log of covered properties */ - protected final ValidationContext context; - /** The name of the property to be validated. */ - protected final String propertyName; - - /** - * Creates a new instance of {@code AbstractPropertyValidator}. - * - *

- * Initializes the validator with the validation context, the property name to validate, and the error code to use - * for reporting validation failures. - *

- * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - */ - AbstractPropertyValidator(final ValidationContext context, final String propertyName) { - this.context = context; - this.propertyName = propertyName; - } - - /** - * Create a new instance of {@code AbstractPropertyValidator} without a property name. - *

- * This is useful for composite validators which are responsible for validation beyond a single property. - *

- * - * @param context validation context containing properties and validation logs - */ - AbstractPropertyValidator(final ValidationContext context) { - this(context, null); - } - - @Override - public String getPropertyName() { - return this.propertyName; - } - - /** - * Retrieve the value of the property associated with the current validator. - * - * @return value of the property or {@code null} if the property is not set - */ - protected String getValue() { - return this.context.getProperties().get(this.propertyName); - } - - /** - * Validate the associated property and returns the validation result. - * - *

- * Records the property name in the validation log before performing the specific validation defined by subclasses. - *

- * - * @return validation result indicating whether the property is valid, along with an associated message if - * validation fails. - */ - public final ValidationResult validate() { - return performSpecificValidation(); - } - - /** - * Validate the specific property according to custom logic implemented by subclasses. - * - *

- * Performed as part of the overall property validation process. This method defines how the property value is - * checked against specific conditions. - *

- * - * @return result of the validation, indicating validity and an associated message if invalid. - */ - protected abstract ValidationResult performSpecificValidation(); - -} diff --git a/src/main/java/com/exasol/adapter/properties/AllOfValidator.java b/src/main/java/com/exasol/adapter/properties/AllOfValidator.java deleted file mode 100644 index adf34c9..0000000 --- a/src/main/java/com/exasol/adapter/properties/AllOfValidator.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.exasol.adapter.properties; - -import java.util.ArrayList; -import java.util.List; - -/** - * Validates a property using multiple sub-validators. - * - *

- * Executes all provided sub-validators sequentially. Collects and merges validation messages from any failing - * sub-validators. Note that validators are executed in the order they are listed in the constructor. - *

- *

- * Always completes all validations, regardless of intermediate failures. - *

- *

- * If you need to combine validators and short-circuit please use {@link AndValidator} instead. - *

- */ -public class AllOfValidator extends AbstractPropertyValidator { - private final PropertyValidator[] validators; - - /** - * Create a new instance of the {@link AllOfValidator}. - * - * @param context validation context containing properties and validation logs - * @param validators array of {@link PropertyValidator} instances to apply sequentially - */ - AllOfValidator(final ValidationContext context, final PropertyValidator... validators) { - super(context); - this.validators = validators; - } - - /** - * Executes all registered sub-validators, even if some validations fail. - * - * @return any validation error message, each on a separate line in the original order the validations were executed - */ - // [impl -> dsn~all-of-validation~1] - @Override - protected ValidationResult performSpecificValidation() { - final List errorMessages = new ArrayList<>(this.validators.length); - boolean isValid = true; - for (final PropertyValidator validator : this.validators) { - final ValidationResult result = validator.validate(); - if (!result.isValid()) { - errorMessages.add(result.getMessage()); - isValid = false; - } - } - return new ValidationResult(isValid, String.join(System.lineSeparator(), errorMessages)); - } -} diff --git a/src/main/java/com/exasol/adapter/properties/AndValidator.java b/src/main/java/com/exasol/adapter/properties/AndValidator.java deleted file mode 100644 index e7f3c4d..0000000 --- a/src/main/java/com/exasol/adapter/properties/AndValidator.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.exasol.adapter.properties; - -/** - * Validator that combines multiple validators using a logical AND operation. - * - *

- * Short-circuits after the first failed validation. Passes validation only if all composed validators succeed. - *

- */ -public class AndValidator extends AbstractPropertyValidator { - private final PropertyValidator[] validators; - - /** - * Constructs an {@code AndValidator} combining multiple property validators. - * - * @param context validation context encapsulating properties and logs - * @param validators array of {@code PropertyValidator} instances to combine using logical AND - */ - AndValidator(final ValidationContext context, final PropertyValidator... validators) { - super(context); - this.validators = validators; - } - - /** - * Performs validation using all assigned validators. - * - *

- * Iterates through all validators, applying each validation. Returns the first failed validation result or a - * successful result if all validations pass. - * - * @return result of the first failed validation or a successful result if all validators pass - */ - // [impl -> dsn~short-circuiting-and-validation~1] - public ValidationResult performSpecificValidation() { - for (PropertyValidator validator : this.validators) { - final ValidationResult result = validator.validate(); - if (!result.isValid()) { - return result; - } - } - return ValidationResult.success(); - } -} diff --git a/src/main/java/com/exasol/adapter/properties/BooleanValidator.java b/src/main/java/com/exasol/adapter/properties/BooleanValidator.java deleted file mode 100644 index 5438284..0000000 --- a/src/main/java/com/exasol/adapter/properties/BooleanValidator.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -import java.util.regex.Pattern; - -/** - * Validates a property value as a boolean string. - * - *

- * Ensures the value is either "true" or "false" (case-insensitive). - */ -public class BooleanValidator extends AbstractPropertyValidator { - private static final Pattern BOOLEAN_REGEX = Pattern.compile("true|false", Pattern.CASE_INSENSITIVE); - - /** - * Constructs a {@code BooleanValidator} to validate boolean property values. - * - *

- * Initializes with a validation context, property name, and error code. - * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - */ - BooleanValidator(final ValidationContext context, final String propertyName) { - super(context, propertyName); - } - - /** - * Validates whether the property value is a valid boolean string. - * - *

- * The value must match the pattern "true" or "false" (case-insensitive). - * - * @return Validation result indicating success if the value is valid, or failure with an error message otherwise. - */ - // [impl -> dsn~validating-boolean-properties~1] - @Override - public ValidationResult performSpecificValidation() { - final String value = this.getValue(); - if ((value != null) && BOOLEAN_REGEX.matcher(value).matches()) - return ValidationResult.success(); - else { - return new ValidationResult(false, - ExaError.messageBuilder("E-VSCOMJAVA-43") - .message("The value {{value}} for property {{property}} must be either 'true' or 'false'.", - value, this.propertyName) - .toString()); - } - } -} diff --git a/src/main/java/com/exasol/adapter/properties/CoverageValidator.java b/src/main/java/com/exasol/adapter/properties/CoverageValidator.java deleted file mode 100644 index cfbbe8d..0000000 --- a/src/main/java/com/exasol/adapter/properties/CoverageValidator.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Validates that all properties provided during schema creation are known. - * - *

- * Ensures mandatory and optional properties are accounted for, preventing unrecognized or unused properties. Detects - * potential issues like misspellings, outdated properties, or invalid property usage for the context. - *

- */ -public class CoverageValidator extends AbstractPropertyValidator { - - /** - * Create a new instance of {@link CoverageValidator}. - * - * @param context validation context containing properties and validation logs - */ - CoverageValidator(final ValidationContext context) { - super(context); - } - - /** - * Check that all properties provided by the users are recognized by at least one validator, including the optional - * ones. - *

- * While mandatory properties have their own explicit checks, everything else is by definition an optional property. - * We want to avoid that users misspell property names, use outdated properties or by mistake try to use properties - * from a different dialect. - *

- *

- * Please note that this does not indicate that all validations were successful. Only that the user provided no - * property for which no validator exists. - *

- * - * @return success if all properties given by the virtual schema user were validated else indicate a failure and - * provide an error message - */ - // [impl -> dsn~validation-completeness-check~1] - @Override - protected ValidationResult performSpecificValidation() { - final Set unknownProperties = this.context.getProperties() // - .keySet() // - .stream() // - .filter(propertyName -> !this.context.isKnownProperty(propertyName)) // - .collect(Collectors.toSet()); - if (unknownProperties.isEmpty()) { - return ValidationResult.success(); - } else { - return new ValidationResult(false, - ExaError.messageBuilder("E-VSCOMJAVA-53") - .message("The following properties are unknown: {{properties}.", - String.join("', '", unknownProperties)) - .mitigation( - "Please check the documentation of the adapter for valid properties and the spelling.") - .toString()); - } - } -} diff --git a/src/main/java/com/exasol/adapter/properties/EnumerationValidator.java b/src/main/java/com/exasol/adapter/properties/EnumerationValidator.java deleted file mode 100644 index 32ac2b4..0000000 --- a/src/main/java/com/exasol/adapter/properties/EnumerationValidator.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -import java.util.ArrayList; -import java.util.List; - -/** - * Validates if a property has a value that matches one of the values in a specified enumeration. - * - *

- * The validator checks if the current property value is a valid enum constant within the given enumeration type. If the - * value is invalid, it produces an appropriate error message. - *

- * - * @param Type of the enumeration to validate against - */ -public class EnumerationValidator> extends AbstractPropertyValidator { - /** Class of the enumeration type being validated */ - protected final Class enumClass; - /** Cache containing the string representations of all enumeration values */ - protected final List enumValueCache; - - /** - * Create a new instance of {@link EnumerationValidator}. - * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property being validated - * @param enumClass enumeration class containing the valid values for the property - */ - EnumerationValidator(final ValidationContext context, final String propertyName, - final Class enumClass) { - super(context, propertyName); - this.enumClass = enumClass; - this.enumValueCache = getEnumValues(); - } - - private List getEnumValues() { - final List enumNames = new ArrayList<>(this.enumClass.getEnumConstants().length); - for (final Enum constant : this.enumClass.getEnumConstants()) { - enumNames.add(constant.name()); - } - return enumNames; - } - - /** - * Validate the property value to ensure it matches one of the predefined enumeration values. - * - * @return result of the validation, either successful or containing an error message - */ - // [impl -> dsn~validating-enumeration-properties~1]] - @Override - protected ValidationResult performSpecificValidation() { - final String value = this.getValue(); - if (value == null || !this.enumValueCache.contains(value)) { - return new ValidationResult(false, ExaError.messageBuilder("E-VSCOMJAVA-44") - .message("The property {{property}} has an invalid value {{value}}.", this.propertyName, value) - .mitigation("Please pick one of the following values: {{values}}", - String.join("', '", this.enumValueCache)) - .toString()); - } else { - return ValidationResult.success(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/ExasolObjectIdValidator.java b/src/main/java/com/exasol/adapter/properties/ExasolObjectIdValidator.java deleted file mode 100644 index 2498447..0000000 --- a/src/main/java/com/exasol/adapter/properties/ExasolObjectIdValidator.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.db.ExasolIdentifier; -import com.exasol.errorreporting.ExaError; - -/** - * Validates that a property value adheres to the format of an Exasol database object identifier. - * - *

- * Checks the property value against the rules for valid Exasol identifiers and returns a validation result. - *

- */ -public class ExasolObjectIdValidator extends AbstractPropertyValidator { - private static final String ID_DOC_URL = "https://docs.exasol.com/db/latest/sql_references/basiclanguageelements.htm#SQLidentifier"; - - /** - * Create a new instance of {@link ExasolObjectIdValidator}. - * - *

- * Validates if the property value adheres to the format requirements for Exasol database object identifiers. - * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - */ - ExasolObjectIdValidator(final ValidationContext context, final String propertyName) { - super(context, propertyName); - } - - /** - * Validate if the property value conforms to the format of an Exasol database object identifier. - * - * @return result of the validation indicating success or failure with an appropriate error message - */ - // [impl -> dsn~validating-exasol-object-id-properties~1] - @Override - protected ValidationResult performSpecificValidation() { - if (ExasolIdentifier.validate(this.getValue())) { - return ValidationResult.success(); - } else { - return new ValidationResult(false, ExaError.messageBuilder("E-VSCOMJAVA-45") // - .message("Property {{property}} is not a valid Exasol database object identifier.", - this.propertyName) // - .mitigation("Please refer to " + ID_DOC_URL + " for the correct format guidelines.") // - .toString()); - } - } -} diff --git a/src/main/java/com/exasol/adapter/properties/IntegerValidator.java b/src/main/java/com/exasol/adapter/properties/IntegerValidator.java deleted file mode 100644 index 770e329..0000000 --- a/src/main/java/com/exasol/adapter/properties/IntegerValidator.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -/** - * Validates a property value as an integer number with optional boundaries. - *

- * Ensures the value is a valid integer and optionally checks if it's within specified upper and lower boundaries. - *

- */ -public class IntegerValidator extends AbstractPropertyValidator { - private final long lowerBound; - private final long upperBound; - private final boolean bounded; - - /** - * Create a new instance of a {@link IntegerValidator} to validate integer property values. - * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - */ - public IntegerValidator(final ValidationContext context, final String propertyName) { - super(context, propertyName); - this.lowerBound = Long.MIN_VALUE; - this.upperBound = Long.MAX_VALUE; - this.bounded = false; - } - - /** - * Creat a new instance of a {@link IntegerValidator} with boundary constraints. - *

- * Initializes with a validation context, property name, error code, and upper and lower boundaries for the integer - * value. - *

- * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - * @param lowerBound minimum allowed value (inclusive) - * @param upperBound maximum allowed value (inclusive) - */ - IntegerValidator(final ValidationContext context, final String propertyName, final long lowerBound, - final long upperBound) { - super(context, propertyName); - this.lowerBound = lowerBound; - this.upperBound = upperBound; - this.bounded = true; - if (upperBound < lowerBound) { - throw new IllegalArgumentException(ExaError // - .messageBuilder("E-VSCOMJAVA-54") - .message( - "The upper bound ({{upper_bound}}) must be greater than or equal the lower bound ({{lower_bound}}) for property {{property}}.", // - upperBound, lowerBound, propertyName) // - .ticketMitigation() // - .toString()); - } - } - - /** - * Validates whether the property value is a valid integer and within specified boundaries if provided. - * - * @return Validation result indicating success if the value is valid, or failure with an error message otherwise. - */ - // [impl -> dsn~validating-integer-properties~1] - // [impl -> dsn~integer-interval-validation~1] - @Override - public ValidationResult performSpecificValidation() { - try { - final long numericValue = Long.parseLong(this.getValue()); - if (this.bounded && (numericValue < this.lowerBound || numericValue > this.upperBound)) { - return new ValidationResult(false, ExaError.messageBuilder("E-VSCOMJAVA-46").message( - "The value for property {{property}} must be between {{lower_bound}} and {{upper_bound}, but was {{value}}.", - this.propertyName, this.lowerBound, this.upperBound, numericValue).toString()); - } - return ValidationResult.success(); - } catch (final NumberFormatException exception) { - return new ValidationResult(false, - ExaError.messageBuilder("E-VSCOMJAVA-47") - .message("The value {{value}} for property {{property}} is not a valid integer number.", - this.getValue(), this.propertyName) - .toString()); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/MultiSelectValidator.java b/src/main/java/com/exasol/adapter/properties/MultiSelectValidator.java deleted file mode 100644 index d9257ee..0000000 --- a/src/main/java/com/exasol/adapter/properties/MultiSelectValidator.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -import java.util.ArrayList; -import java.util.List; - -/** - * Property validator that validates that the value of a give property is a sub-set of the values of an enumeration. - * - * @param enumeration that the values are checked against - */ -public class MultiSelectValidator> extends EnumerationValidator { - // The following regular expression intentionally limits the number of allowed spaces to avoid RegEx DOS. - private static final String COMMA_SPLIT_REGEX = "\\s{0,10},\\s{0,10}"; - private final boolean emptyAllowed; - - /** - * Constructs a new instance of {@code MultiSelectValidator}. - *

- * This validator is used for validating properties whose values can be selected from an enumeration of predefined - * choices. - *

- * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - * @param enumClass {@code Class} object corresponding to the enumeration type from which valid values can be - * selected - * @param emptyAllowed controls if empty values or values that are split at the commas are allowed to be empty. - */ - public MultiSelectValidator(final ValidationContext context, final String propertyName, - final Class enumClass, final boolean emptyAllowed) { - super(context, propertyName, enumClass); - this.emptyAllowed = emptyAllowed; - } - - /** - * Constructs a new instance of {@code MultiSelectValidator}. - *

- * This validator is used for validating properties whose values can be selected from an enumeration of predefined - * choices. Empty values are not allowed. - *

- * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - * @param enumClass {@code Class} object corresponding to the enumeration type from which valid values can be - * selected - */ - MultiSelectValidator(final ValidationContext context, final String propertyName, final Class enumClass) { - this(context, propertyName, enumClass, false); - } - - // [impl -> dsn~validating-multi-select-properties~1] - @Override - protected ValidationResult performSpecificValidation() { - if (this.getValue() == null) { - return validateNull(); - } else { - return validateNotNull(); - } - } - - private ValidationResult validateNull() { - return validateEmptyInput(); - } - - private ValidationResult validateEmptyInput() { - return this.emptyAllowed - ? ValidationResult.success() - : createEmptyValueFailureResult(); - } - - private ValidationResult createEmptyValueFailureResult() { - return ValidationResult.failure(ExaError.messageBuilder("E-VSCOMJAVA-56") - .message("The property {{property}} must have at least one value set.", this.propertyName) - .mitigation("Please select at least one of the following values: {{values}}." - + " Separate the individual values with a comma.", - String.join("', '", this.enumValueCache)) - .toString()); - } - - private ValidationResult validateNotNull() { - if (this.getValue().isBlank()) { - return validateEmptyInput(); - } else { - final String[] givenValues = this.getValue().trim().split(COMMA_SPLIT_REGEX); - if (givenValues.length == 0) { - return validateEmptyInput(); - } else { - return validateNonEmptyValueList(givenValues); - } - } - } - - private ValidationResult validateNonEmptyValueList(final String[] givenValues) { - final List unknownValues = new ArrayList<>(); - for (final String givenValue : givenValues) { - if (!this.enumValueCache.contains(givenValue)) { - unknownValues.add(givenValue); - } - } - return unknownValues.isEmpty() - ? ValidationResult.success() - : ValidationResult.failure(ExaError.messageBuilder("E-VSCOMJAVA-57") - .message( - "The following values given for the property {{property}} are unknown: {{unknown}}", - this.propertyName, String.join("', '", unknownValues)) - .mitigation("Please use one or more of the following values: {{values}}." - + " Separate the individual values with a comma.", - String.join("', '", this.enumValueCache)) - .toString()); - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/PropertyValidator.java b/src/main/java/com/exasol/adapter/properties/PropertyValidator.java deleted file mode 100644 index 35998c4..0000000 --- a/src/main/java/com/exasol/adapter/properties/PropertyValidator.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.exasol.adapter.properties; - -/** - * Interface for validating properties. - *

- * The {@code PropertyValidator} interface defines the contract for validating properties. - * Implementations of this interface encapsulate the logic for verifying whether a property - * meets specific criteria. The result of the validation process is encapsulated in a - *

- * {@link ValidationResult} object. - */ -public interface PropertyValidator { - /** - * Validate the property and returns the result of the validation. - *

- * This method evaluates the value of a property to determine whether it adheres - * to the expected criteria. Implementations of this method define the specific - * validation logic. The outcome of the validation process is encapsulated in an - * instance of {@link ValidationResult}, which includes details about - * whether the validation was successful and any associated messages. - *

- * @return an {@link ValidationResult} object representing the result of the validation, - * including whether the validation was successful and any accompanying message - */ - public ValidationResult validate(); - - /** - * Retrieve the name of the property being validated. - * - * @return name of the property as a string - */ - public String getPropertyName(); -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/RequiredValidator.java b/src/main/java/com/exasol/adapter/properties/RequiredValidator.java deleted file mode 100644 index 2410c5a..0000000 --- a/src/main/java/com/exasol/adapter/properties/RequiredValidator.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -/** - * Validator for ensuring that a required property is set and not empty. - */ -public class RequiredValidator extends AbstractPropertyValidator { - private final boolean required; - - /** - * Create a new instance of a {@link RequiredValidator} - * - * @param context validation context with properties and validation log - * @param propertyName name of the property to be validated - * @param required {@code true} the property need to be set, {@code false} otherwise - */ - public RequiredValidator(final ValidationContext context, final String propertyName, final boolean required) { - super(context, propertyName); - this.required = required; - } - - /** - * Create a new instance of a {@link RequiredValidator} - * - * @param context validation context with properties and validation log - * @param propertyName name of the property to be validated - */ - RequiredValidator(final ValidationContext context, final String propertyName) { - this(context, propertyName, true); - } - - /** - * Verify that the property is set as required. - *

- * A property counts as set if it exists (i.e. the value is not {@code null}) and not an empty string. - *

- * - * @return success in case the property is set, failed validation with an error message indicating the issue - * otherwise - */ - @Override - public ValidationResult performSpecificValidation() { - if (this.required) { - return validatePropertyIsSet(); - } else { - return validatePropertyIsNotSet(); - } - } - - // [impl -> dsn~validating-the-existence-of-mandatory-properties~1] - private ValidationResult validatePropertyIsSet() { - if (this.getValue() == null) { - return new ValidationResult(false, ExaError.messageBuilder("E-VSCOMJAVA-48") - .message("The mandatory property {{property}} is missing.", this.propertyName).toString()); - } else if (this.getValue().isEmpty()) { - return new ValidationResult(false, ExaError.messageBuilder("E-VSCOMJAVA-49") - .message("The mandatory property {{property}} is empty.", this.propertyName).toString()); - } else { - return ValidationResult.success(); - } - } - - // [impl -> dsn~validating-the-absence-of-unwanted-properties~1] - private ValidationResult validatePropertyIsNotSet() { - if (this.getValue() == null || this.getValue().isEmpty()) { - return ValidationResult.success(); - } else { - return new ValidationResult(false, - ExaError.messageBuilder("E-VSCOMJAVA-50") - .message("The unwanted property {{property}} is set.", this.propertyName) - .mitigation("Please remove the property.").toString()); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/StringValidator.java b/src/main/java/com/exasol/adapter/properties/StringValidator.java deleted file mode 100644 index 9552f4d..0000000 --- a/src/main/java/com/exasol/adapter/properties/StringValidator.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -import java.util.regex.Pattern; - -/** - * Validator that matches a string property against a regular expression pattern. - */ -public class StringValidator extends AbstractPropertyValidator { - private final Pattern pattern; - private final String formatDescription; - - /** - * Create a new instance of {@link StringValidator}. - * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - * @param pattern regular expression the property value must match - * @param formatDescription description of the expected format to include in error messages - */ - StringValidator(final ValidationContext context, final String propertyName, - final Pattern pattern, final String formatDescription) { - super(context, propertyName); - this.pattern = pattern; - this.formatDescription = formatDescription; - } - - /** - * Create a new instance of {@link StringValidator}. - * - * @param context validation context containing properties and validation logs - * @param propertyName name of the property to validate - * @param pattern regular expression the property value must match - */ - StringValidator(final ValidationContext context, final String propertyName, final Pattern pattern) { - this(context, propertyName, pattern, null); - } - - /** - * Validate the property value against the configured regular expression pattern. - * - *

- * If the value matches the pattern, the validation succeeds. If it does not match, the validation fails and - * provides an error message. - *

- *

- * If a format description is given during construction, the error message will use that, otherwise it will display - * the regular expression. - *

- * - * @return validation result containing success or failure status and an associated message - */ - // [impl -> dsn~validating-a-string-against-a-regular-expression~1] - // [impl -> dsn~reporting-format-violations-in-properties~1] - @Override - protected ValidationResult performSpecificValidation() { - if (this.pattern.matcher(this.getValue()).matches()) { - return ValidationResult.success(); - } else { - if (this.formatDescription == null) { - return new ValidationResult(false, - ExaError.messageBuilder("E-VSCOMJAVA-51") - .message("The property {{property}} has an invalid value {{value}}.", - this.propertyName, this.getValue()) - .mitigation("Please use a value matching the regular expression '{{pattern}}'.", - this.pattern) - .toString()); - } else { - return new ValidationResult(false, - ExaError.messageBuilder("E-VSCOMJAVA-55") - .message("The property {{property}} has an invalid value {{value}}.", - this.propertyName, this.getValue()) - .mitigation("Please use the format {{format}}.", this.formatDescription).toString()); - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/UnixPathValidator.java b/src/main/java/com/exasol/adapter/properties/UnixPathValidator.java deleted file mode 100644 index 5eb1817..0000000 --- a/src/main/java/com/exasol/adapter/properties/UnixPathValidator.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.exasol.adapter.properties; - -import com.exasol.errorreporting.ExaError; - -import java.nio.file.InvalidPathException; -import java.nio.file.Path; - -/** - * Validator for Unix Paths. - *

- * Some virtual schema adapters require files in BucketFS and paths to them in an adapter property. This validator's - * purpose is to restrict path content to safe paths inside the change root of a BucketFS bucket. - *

- */ -public class UnixPathValidator extends AbstractPropertyValidator { - UnixPathValidator(final ValidationContext context, final String propertyName) { - super(context, propertyName); - } - - /** - * Validates the value of the associated property to ensure it represents a valid Unix file path. - * - *

- * This method performs the following validations: - *

- *
    - *
  1. Checks for forbidden path content such as path traversal characters ('.', '..', '//'), null characters, or - * any other invalid sequences.
  2. - *
  3. Tries to parse the property value into a {@link Path} to confirm it is a valid path.
  4. - *
- *

- * If the property fails validation, a failure {@code ValidationResult} is returned that encapsulates the details of - * the failed validation and includes a mitigation message. If the property passes all checks, a success - * {@code ValidationResult} is returned. - *

- * - * @return success if the value is a valid Unix path, or failure with an appropriate error message if the validation - * fails. - */ - // [impl -> dsn~validating-unix-paths~1] - @Override - protected ValidationResult performSpecificValidation() { - final String value = this.getValue(); - if (hasForbiddenPathContent(value)) { - return createFailureMessage(); - } else { - try { - // Path.of catches typical illegal contents like null characters. - // Note that Path.of() behaves differently on different OSes. - // But since UDFs run on Exasol worker nodes, Linux paths are validated. - Path.of(value); - } catch (final InvalidPathException exception) { - return createFailureMessage(); - } - return ValidationResult.success(); - } - } - - // [impl -> dsn~only-absolute-paths-are-valid-in-properties~1] - private boolean hasForbiddenPathContent(final String value) { - return value.isBlank() // - || !value.startsWith("/") // We restrict all paths to absolute - || value.contains("//") // - || value.contains("/./") // - || value.contains("/../") // - || value.contains("%") // - || value.contains("\t") // - || value.contains("\n") // - || value.contains("\r") // - || value.contains("\\") // - || value.endsWith("/.") // - || value.endsWith("/..") // - || value.contains(":"); - } - - private ValidationResult createFailureMessage() { - return ValidationResult.failure( - ExaError.messageBuilder("E-VSCOMJAVA-59") - .message("The property {{property}} contains an invalid path: {{path}}.", - this.propertyName, this.getValue()) - .mitigation("Make sure you provide an absolute path.") - .mitigation("Don't use a protocol specifier (like 'http:').") - .mitigation("Avoid blank paths, tabs, newlines and carriage returns.") - .mitigation("Please remove any kind of path traversal characters ('.', '..', '//').") - .toString() - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/ValidationContext.java b/src/main/java/com/exasol/adapter/properties/ValidationContext.java deleted file mode 100644 index 74c9b3c..0000000 --- a/src/main/java/com/exasol/adapter/properties/ValidationContext.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.exasol.adapter.properties; - -import java.util.*; - -/** - * Encapsulates properties and validation logs for property validation. - * - *

- * Provides context required during validation, including access to adapter properties and a log for tracking validated - * properties. The context also holds a list of known properties, which helps the validators find properties that are - * misspelled or generally not allowed in a virtual schema dialect. - *

- */ -public class ValidationContext { - private final AdapterProperties properties; - private final Set knownProperties = new HashSet<>(); - - /** - * Create a new instance of {@link ValidationContext}. - * - * @param properties adapter properties for validation - */ - public ValidationContext(final AdapterProperties properties) { - this.properties = properties; - } - - /** - * Get the adapter properties. - * - * @return adapter properties - */ - public AdapterProperties getProperties() { - return this.properties; - } - - /** - * Retrieve the set of property names that the virtual schema adapter knows. - * - * @return set of known property names - */ - public Set getKnownProperties() { - return this.knownProperties; - } - - /** - * Add a single property name to the known properties list. - * - *

- * Expands the list of property names recognized in the validation context. Use for adding a single property. - *

- * - * @param knownProperties name of the property to be added - */ - public void addKnownProperty(final String knownProperties) { - this.knownProperties.add(knownProperties); - } - - /** - * Check if a property name exists in the list of known properties. - * - * @param propertyName name of the property to check - * - * @return true if the property name is in the known properties list, false otherwise - */ - public boolean isKnownProperty(final String propertyName) { - return this.knownProperties.contains(propertyName); - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/ValidationLog.java b/src/main/java/com/exasol/adapter/properties/ValidationLog.java deleted file mode 100644 index 95cdaa2..0000000 --- a/src/main/java/com/exasol/adapter/properties/ValidationLog.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.exasol.adapter.properties; - -import java.util.ArrayList; -import java.util.List; - -/** - * Logs and tracks the properties that have been validated. - */ -public class ValidationLog { - final List validatedProperties = new ArrayList<>(); - - /** - * Create a new instance of {@link ValidationLog}. - */ - public ValidationLog() { - // Empty constructor to add JavaDoc. - } - - /** - * Register a property as validated. - * - * @param propertyName name of the property to mark as validated - */ - public void addValidation(final String propertyName) { - validatedProperties.add(propertyName); - } - - /** - * Check if a property has been validated. - * - * @param property name of the property to check - * - * @return true if the property has been validated, false otherwise - */ - public boolean isValidated(final String property) { - return validatedProperties.contains(property); - } - - /** - * Retrieve the list of properties that have been validated. - * - * @return list of validated property names - */ - public List getValidatedProperties() { - return validatedProperties; - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/ValidationResult.java b/src/main/java/com/exasol/adapter/properties/ValidationResult.java deleted file mode 100644 index c3d7e79..0000000 --- a/src/main/java/com/exasol/adapter/properties/ValidationResult.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.exasol.adapter.properties; - -/** - * Represents the result of a property validation. - *

- * This class is used to encapsulate the outcome of a property validation process, which includes whether the validation - * was successful and an associated message describing the result. - *

- */ -public class ValidationResult { - private final boolean valid; - private final String message; - - /** - * Creates a new instance of {@code PropertyValidationResult}. - * - * @param valid indicates whether the property validation was successful - * @param message message providing additional information about the validation result - */ - public ValidationResult(boolean valid, final String message) { - this.valid = valid; - this.message = message; - } - - /** - * Convenience factory method for a result representing a successful validation. - * - * @return successful validation result - */ - public static ValidationResult success() { - return new ValidationResult(true, ""); - } - - /** - * Convenience factory method for a result indicating a failed validation. - * - * @param errorMessage error message that explains the validation failure - * - * @return failed validation result - */ - public static ValidationResult failure(final String errorMessage) { - return new ValidationResult(false, errorMessage); - } - - /** - * Checks whether the property validation was successful. - * - * @return {code true} if the property validation was successful; {@code false} otherwise - */ - public boolean isValid() { - return this.valid; - } - - /** - * Retrieves the validation message associated with this result. - * - * @return message providing additional information about the validation result - */ - public String getMessage() { - return message; - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/properties/ValidatorFactory.java b/src/main/java/com/exasol/adapter/properties/ValidatorFactory.java deleted file mode 100644 index a87e14a..0000000 --- a/src/main/java/com/exasol/adapter/properties/ValidatorFactory.java +++ /dev/null @@ -1,267 +0,0 @@ -package com.exasol.adapter.properties; - -import java.util.regex.Pattern; - -/** - * Factory for creating property validators. - * - *

- * Provides methods to create validators for various types of properties and validation rules. - *

- */ -// [impl -> dsn~validator-composition~1] -public class ValidatorFactory { - private final ValidationContext context; - - /** - * Create a new instance of {@link ValidatorFactory}. - * - * @param properties virtual schema properties to be validated - * @return instance of {@link ValidatorFactory} - */ - public static ValidatorFactory create(final AdapterProperties properties) { - return new ValidatorFactory(new ValidationContext(properties)); - } - - /** - * Create a new instance of the {@link ValidatorFactory}. - * - * @param context validation context containing adapter properties and validation log - */ - private ValidatorFactory(final ValidationContext context) { - this.context = context; - } - - /** - * Create a validator for a boolean value. - * - * @param propertyName name of the property to validate - * @return validator for verifying a boolean property - */ - public PropertyValidator bool(final String propertyName) { - this.context.addKnownProperty(propertyName); - return new BooleanValidator(this.context, propertyName); - } - - /** - * Create a validator to validate that a property's value is a valid integer. - * - * @param propertyName name of the property to validate - * @return validator for validating an integer property - */ - public PropertyValidator integer(final String propertyName) { - this.context.addKnownProperty(propertyName); - return new IntegerValidator(this.context, propertyName); - } - - /** - * Create a validator to validate that a property's value is a valid integer and optionally within a range. - * - * @param propertyName name of the property to validate - * @param min minimum allowable value (inclusive) - * @param max maximum allowable value (inclusive) - * @return validator for validating an integer property with range constraints - */ - public PropertyValidator integer(final String propertyName, final long min, final long max) { - this.context.addKnownProperty(propertyName); - return new IntegerValidator(this.context, propertyName, min, max); - } - - /** - * Creates a validator that checks whether the property matches a pattern - * - * @param propertyName name of the property to validate - * @param pattern regular expression pattern to match the property value against - * @return validator that checks that a property - */ - public StringValidator matches(final String propertyName, final Pattern pattern) { - this.context.addKnownProperty(propertyName); - return new StringValidator(this.context, propertyName, pattern); - } - - /** - * Create a validator that checks whether the value of the given property is a valid enum value. - *

- * Please note that due to type erasure in Java generics, we have logical duplication here between the type - * parameter and the class of the enum. This is unfortunately unavoidable. - *

- * - * @param propertyName name of the property to validate - * @param enumClass enum to check against - * @param enum type - * @return validator for checking that the given value is contained in the enum - */ - public > PropertyValidator enumeration(final String propertyName, - final Class enumClass) { - this.context.addKnownProperty(propertyName); - return new EnumerationValidator<>(this.context, propertyName, enumClass); - } - - /** - * Creates a validator for validating a multi-select property. - *

- * Please note that due to type erasure in Java generics, we have logical duplication here between the type - * parameter and the class of the enum. This is unfortunately unavoidable. Empty values or values that are empty - * after splitting at the commas are invalid. - *

- * - * @param propertyName name of the property to validate - * @param enumClass enum defining the allowed values - * @param type of the enum defining the allowed values for the property - * - * @return an instance of {@code MultiSelectValidator} for validating the property - */ - public > PropertyValidator multiSelect(final String propertyName, - final Class enumClass) { - return new MultiSelectValidator<>(this.context, propertyName, enumClass); - } - - /** - * Creates a validator for validating a multi-select property, allowing an empty value. - *

- * Please note that due to type erasure in Java generics, we have logical duplication here between the type - * parameter and the class of the enum. This is unfortunately unavoidable. - *

- * - * @param propertyName name of the property to validate - * @param enumClass enum defining the allowed values - * @param type of the enum defining the allowed values for the property - * - * @return an instance of {@code MultiSelectValidator} for validating the property - */ - public > PropertyValidator multiSelectEmptyAllowed(final String propertyName, - final Class enumClass) { - return new MultiSelectValidator<>(this.context, propertyName, enumClass, true); - } - - /** - * Create a validator for an Exasol object identifier property. - * - *

- * Validates that the property value conforms to the format rules for Exasol database object identifiers. - *

- * - * @param propertyName name of the property to validate - * @return validator for verifying an Exasol object identifier - */ - public PropertyValidator exasolObjectId(final String propertyName) { - this.context.addKnownProperty(propertyName); - return new ExasolObjectIdValidator(this.context, propertyName); - } - - /** - * Create a validator to enforce the presence of a mandatory property. - *

- * Validates that the specified property exists and is not empty. - *

- * - * @param propertyName name of the property to validate - * @return validator for verifying the mandatory property - */ - public PropertyValidator required(final String propertyName) { - this.context.addKnownProperty(propertyName); - return new RequiredValidator(this.context, propertyName); - } - - /** - * Create a composite validator that combines a {@link RequiredValidator} with the specified validator. - * - *

- * This method acts as a shorthand for combining a {@link RequiredValidator} and the given validator using a - * short-circuiting {@link AndValidator}. The composite validator ensures that: - *

- *
    - *
  • The property is present and not empty (enforced by the {@link RequiredValidator}).
  • - *
  • The property satisfies the additional conditions defined by the given validator.
  • - *
- *

- * The validation short-circuits and stops as soon as the {@link RequiredValidator} fails, ensuring efficiency. - *

- * - * @param validator the validator to combine with the {@link RequiredValidator} - * @return a composite validator ensuring the property is required and satisfies the additional validation rules - */ - public PropertyValidator required(final PropertyValidator validator) { - this.context.addKnownProperty(validator.getPropertyName()); - return and(new RequiredValidator(this.context, validator.getPropertyName()), - validator); - } - - /** - * Create a validator that makes sure an unwanted property is not set. - * - * @param propertyName name of the property to validate - * @return validator for verifying the unwanted property is not set - */ - public PropertyValidator unwanted(final String propertyName) { - this.context.addKnownProperty(propertyName); - return new RequiredValidator(this.context, propertyName, false); - } - - /** - * Create a validator to check if a property's value matches a regular expression pattern. - * - * @param propertyName name of the property to validate - * @param pattern regular expression the property value must match - * @param formatDescription description of the expected format - * @return validator for validating a property against a regular expression - */ - public StringValidator matches(final String propertyName, final Pattern pattern, - final String formatDescription) { - this.context.addKnownProperty(propertyName); - return new StringValidator(this.context, propertyName, pattern, formatDescription); - } - - /** - * Create a validator to check if a property's value is a Unix path. - * - * @param propertyName name of the property to validate - * @return validator checking that a property is a valid Unix path - */ - public UnixPathValidator absolutePath(final String propertyName) { - return new UnixPathValidator(this.context, propertyName); - } - - /** - * Create a validator that applies all given sub-validators sequentially. - * - * @param validators sub-validators that all must succeed - * @return validator that executes sub-validators sequentially - */ - public PropertyValidator allOf(final PropertyValidator... validators) { - for (final PropertyValidator validator : validators) { - this.context.addKnownProperty(validator.getPropertyName()); - } - return new AllOfValidator(this.context, validators); - } - - /** - * Create a validator that applies multiple sub-validators sequentially. - *

- * The validation short-circuits after the first failed validation. - *

- * - * @param validators sub-validators that all must succeed - * @return validator that executes sub-validators sequentially - */ - public PropertyValidator and(final PropertyValidator... validators) { - for (final PropertyValidator validator : validators) { - this.context.addKnownProperty(validator.getPropertyName()); - } - return new AndValidator(this.context, validators); - } - - /** - * Create a validator for ensuring that all properties provided during schema creation are covered by a validator. - *

- * Validates that all mandatory and optional properties are considered, preventing unused or unexpected properties. - * What this does not mean is that all validators really ran. Short-circuiting validators like the - * {@link AndValidator} can skip validations. - *

- * - * @return validator instance for checking the completeness of property validation - */ - public PropertyValidator allCovered() { - return new CoverageValidator(this.context); - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/request/LoggingConfiguration.java b/src/main/java/com/exasol/adapter/request/LoggingConfiguration.java index 0904c13..bf33a30 100644 --- a/src/main/java/com/exasol/adapter/request/LoggingConfiguration.java +++ b/src/main/java/com/exasol/adapter/request/LoggingConfiguration.java @@ -1,7 +1,7 @@ package com.exasol.adapter.request; -import static com.exasol.adapter.properties.AdapterProperties.DEBUG_ADDRESS_PROPERTY; -import static com.exasol.adapter.properties.AdapterProperties.LOG_LEVEL_PROPERTY; +import static com.exasol.adapter.AdapterProperties.DEBUG_ADDRESS_PROPERTY; +import static com.exasol.adapter.AdapterProperties.LOG_LEVEL_PROPERTY; import java.io.Serializable; import java.util.Map; @@ -14,13 +14,13 @@ public final class LoggingConfiguration implements Serializable { private static final long serialVersionUID = 1930189191497837644L; private static final int DEFAULT_REMOTE_LOGGING_PORT = 3000; private static final Level DEFAULT_LOG_LEVEL = Level.INFO; - /** @serial {@code true} if the adapter should send its log messages to a remote log receiver */ + /** {@code true} if the adapter should send its log messages to a remote log receiver */ private final boolean logRemotely; - /** @serial Name host name where the log receiver listens */ + /** Name host name where the log receiver listens */ private final String host; - /** @serial Remote logging port */ + /** Remote logging port */ private final int port; - /** @serial Log level */ + /** Log level */ private final Level level; private LoggingConfiguration(final Level level, final boolean logRemotely, final String host, final int port) { diff --git a/src/test/java/com/exasol/adapter/properties/AbstractAdapterPropertiesTest.java b/src/test/java/com/exasol/adapter/AbstractAdapterPropertiesTest.java similarity index 81% rename from src/test/java/com/exasol/adapter/properties/AbstractAdapterPropertiesTest.java rename to src/test/java/com/exasol/adapter/AbstractAdapterPropertiesTest.java index 8715c94..080d95c 100644 --- a/src/test/java/com/exasol/adapter/properties/AbstractAdapterPropertiesTest.java +++ b/src/test/java/com/exasol/adapter/AbstractAdapterPropertiesTest.java @@ -1,17 +1,13 @@ -package com.exasol.adapter.properties; +package com.exasol.adapter; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.*; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; class AbstractAdapterPropertiesTest { private Map rawProperties; @@ -39,26 +35,32 @@ void testAssertContainsKeyFalse() { assertThat(properties.containsKey("unexpected"), equalTo(false)); } + @Test + @java.lang.SuppressWarnings("java:S5976") + void testIsEnabledTrue() { + this.rawProperties.put("switch", "TRUE"); + final DummyAdapterProperties properties = new DummyAdapterProperties(this.rawProperties); + assertThat(properties.isEnabled("switch"), equalTo(true)); + } + @Test void testIsEnabledFalseIfPropertyDoesNotExist() { final DummyAdapterProperties properties = new DummyAdapterProperties(this.rawProperties); assertThat(properties.isEnabled("switch"), equalTo(false)); } - private static Stream provideIsEnabledTestCases() { - return Stream.of( - Arguments.of("TRUE", true), - Arguments.of(null, false), - Arguments.of("false", false) - ); + @Test + void testIsEnabledFalseIfPropertyIsNull() { + this.rawProperties.put("switch", null); + final DummyAdapterProperties properties = new DummyAdapterProperties(this.rawProperties); + assertThat(properties.isEnabled("switch"), equalTo(false)); } - @ParameterizedTest - @MethodSource("provideIsEnabledTestCases") - void testIsEnabled(final String propertyValue, final boolean expectedResult) { - this.rawProperties.put("switch", propertyValue); + @Test + void testIsEnabledFalseIfPropertyIsNotTrue() { + this.rawProperties.put("switch", "false"); final DummyAdapterProperties properties = new DummyAdapterProperties(this.rawProperties); - assertThat(properties.isEnabled("switch"), equalTo(expectedResult)); + assertThat(properties.isEnabled("switch"), equalTo(false)); } @Test diff --git a/src/test/java/com/exasol/adapter/properties/AdapterPropertiesTest.java b/src/test/java/com/exasol/adapter/AdapterPropertiesTest.java similarity index 96% rename from src/test/java/com/exasol/adapter/properties/AdapterPropertiesTest.java rename to src/test/java/com/exasol/adapter/AdapterPropertiesTest.java index 51c59b6..1d80142 100644 --- a/src/test/java/com/exasol/adapter/properties/AdapterPropertiesTest.java +++ b/src/test/java/com/exasol/adapter/AdapterPropertiesTest.java @@ -1,6 +1,6 @@ -package com.exasol.adapter.properties; +package com.exasol.adapter; -import static com.exasol.adapter.properties.AdapterProperties.*; +import static com.exasol.adapter.AdapterProperties.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -88,7 +88,8 @@ void testHasNamedPropertyFalseByDefault(final String propertyName) throws NoSuch assertThat(hasNamedProperty, equalTo(false)); } - public static Stream getAdapterPropertyNames() { + @java.lang.SuppressWarnings("java:S1124") + static public Stream getAdapterPropertyNames() { return Arrays.stream(AdapterProperties.class.getDeclaredFields()) // .map(Field::getName) // .filter(name -> name.endsWith(PROPERTY_SUFFIX)) // diff --git a/src/test/java/com/exasol/adapter/properties/AbstractPropertyValidatorTest.java b/src/test/java/com/exasol/adapter/properties/AbstractPropertyValidatorTest.java deleted file mode 100644 index 0b2f4c8..0000000 --- a/src/test/java/com/exasol/adapter/properties/AbstractPropertyValidatorTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.exasol.adapter.properties; - -import java.util.Collections; -import java.util.Map; - -public abstract class AbstractPropertyValidatorTest { - protected ValidationContext createContext(final Map properties) { - return new ValidationContext(new AdapterProperties(properties)); - } - - protected ValidatorFactory createValidatorFactoryWithProperties(final Map properties) { - return ValidatorFactory.create(new AdapterProperties(properties)); - } - - protected ValidatorFactory createValidatorFactoryWithProperties(final String p1, final String v1) { - return createValidatorFactoryWithProperties(Map.of(p1, v1)); - } - - protected ValidatorFactory createValidatorFactoryWithProperties(final String p1, final String v1, final String p2, final String v2) { - return createValidatorFactoryWithProperties(Map.of(p1, v1, p2, v2)); - } - - protected ValidatorFactory createValidatorFactoryWithProperties(final String p1, final String v1, final String p2, final String v2, final String p3, final String v3) { - return createValidatorFactoryWithProperties(Map.of(p1, v1, p2, v2, p3, v3)); - } - - protected ValidatorFactory createValidatorFactoryWithEmptyProperties() { - return createValidatorFactoryWithProperties(Collections.emptyMap()); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/AllOfValidatorTest.java b/src/test/java/com/exasol/adapter/properties/AllOfValidatorTest.java deleted file mode 100644 index e8b3e85..0000000 --- a/src/test/java/com/exasol/adapter/properties/AllOfValidatorTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.assertAll; - -class AllOfValidatorTest extends AbstractPropertyValidatorTest { - // [utest -> dsn~all-of-validation~1] - @Test - void testWhenAllValidationsAreSuccessfulThenTheCombinationIsSuccessful() { - final ValidatorFactory factory = createValidatorFactoryWithEmptyProperties(); - final PropertyValidator mockValidator = mock(PropertyValidator.class); - when(mockValidator.validate()).thenReturn(ValidationResult.success()); - final ValidationResult result = factory.allOf(mockValidator, mockValidator).validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~all-of-validation~1] - @Test - void testWhenFirstValidationFailsThenCombinationFailsButProcessesAllValidators() { - final ValidatorFactory factory = createValidatorFactoryWithEmptyProperties(); - final PropertyValidator mockValidator1 = mock(PropertyValidator.class); - final PropertyValidator mockValidator2 = mock(PropertyValidator.class); - when(mockValidator1.validate()).thenReturn(new ValidationResult(false, "First validation failed")); - when(mockValidator2.validate()).thenReturn(ValidationResult.success()); - final ValidationResult result = factory.allOf(mockValidator1, mockValidator2).validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), equalTo("First validation failed")), - () -> verify(mockValidator2, times(1)).validate()); - } - - // [utest -> dsn~all-of-validation~1] - @Test - void testWhenMultipleValidationsFailThenCombinationCollectsAllErrors() { - final ValidatorFactory factory = createValidatorFactoryWithEmptyProperties(); - final PropertyValidator mockValidator1 = mock(PropertyValidator.class); - final PropertyValidator mockValidator2 = mock(PropertyValidator.class); - final PropertyValidator mockValidator3 = mock(PropertyValidator.class); - when(mockValidator1.validate()).thenReturn(new ValidationResult(false, "First validation failed")); - when(mockValidator2.validate()).thenReturn(new ValidationResult(false, "Second validation failed")); - when(mockValidator3.validate()).thenReturn(ValidationResult.success()); - final ValidationResult result = factory.allOf(mockValidator1, mockValidator2, mockValidator3) - .validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage().contains("First validation failed"), equalTo(true)), - () -> assertThat(result.getMessage().contains("Second validation failed"), equalTo(true)), - () -> verify(mockValidator3, times(1)).validate()); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/AndValidatorTest.java b/src/test/java/com/exasol/adapter/properties/AndValidatorTest.java deleted file mode 100644 index 1818789..0000000 --- a/src/test/java/com/exasol/adapter/properties/AndValidatorTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class AndValidatorTest extends AbstractPropertyValidatorTest { - // [utest -> dsn~short-circuiting-and-validation~1] - @Test - void testWhenFirstAndSecondValidationAreSuccessfulThenTheCombinationIsSuccessful() { - final PropertyValidator mockValidator = mock(PropertyValidator.class); - when(mockValidator.validate()).thenReturn(ValidationResult.success()); - final ValidationResult result = createValidatorFactoryWithEmptyProperties() - .and(mockValidator, mockValidator).validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~short-circuiting-and-validation~1] - @Test - void testWhenFirstValidationFailsThenTheCombinationFailsAtThatPoint() { - final PropertyValidator mockValidator1 = mock(PropertyValidator.class); - final PropertyValidator mockValidator2 = mock(PropertyValidator.class); - when(mockValidator1.validate()).thenReturn(new ValidationResult(false, "First validation failed")); - when(mockValidator2.validate()).thenReturn(ValidationResult.success()); - final ValidationResult result = createValidatorFactoryWithEmptyProperties() - .and(mockValidator1, mockValidator2).validate(); - assertThat(result.isValid(), equalTo(false)); - assertThat(result.getMessage(), equalTo("First validation failed")); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/BooleanPropertyValidatorTest.java b/src/test/java/com/exasol/adapter/properties/BooleanPropertyValidatorTest.java deleted file mode 100644 index 5fd8e20..0000000 --- a/src/test/java/com/exasol/adapter/properties/BooleanPropertyValidatorTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; - -class BooleanPropertyValidatorTest extends AbstractPropertyValidatorTest { - // [utest -> dsn~validating-boolean-properties~1] - @ValueSource(strings = { "TRUE", "FALSE", "true", "false" }) - @ParameterizedTest - void testWhenPropertyContainsValidValueThenValidationSucceeds(final String value) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("P1", value); - final ValidationResult result = factory.bool("P1").validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-boolean-properties~1] - @Test - void testWhenPropertyContainsInvalidValueThenValidationFails() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("P1", "SOME STRING"); - final ValidationResult result = factory.bool("P1").validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), () -> assertThat(result.getMessage(), equalTo( - "E-VSCOMJAVA-43: The value 'SOME STRING' for property 'P1' must be either 'true' or 'false'."))); - } - - @Test - void testWhenPropertyIsNullThenValidationFails() { - final Map rawProperties = new HashMap<>(); - rawProperties.put("P1", null); - final ValidatorFactory factory = ValidatorFactory.create(new AdapterProperties(rawProperties)); - final ValidationResult result = factory.bool("P1").validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-43: The value for property 'P1' must be either 'true' or 'false'."))); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/CoverageValidatorTest.java b/src/test/java/com/exasol/adapter/properties/CoverageValidatorTest.java deleted file mode 100644 index 8db6575..0000000 --- a/src/test/java/com/exasol/adapter/properties/CoverageValidatorTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertAll; - -class CoverageValidatorTest extends AbstractPropertyValidatorTest { - // [utest -> dsn~validation-completeness-check~1] - @Test - void testWhenAllPropertiesWereValidatedThenIndicateSuccess() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("P1", "true", "P2", "false"); - factory.bool("P1"); - factory.bool("P2"); - final PropertyValidator validator = factory.allCovered(); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validation-completeness-check~1] - @Test - void testWhenNotAllPropertiesAreKnownThenIndicateFailure() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("P1", "true", "P2", "V2", "P3", "V3"); - factory.bool("P1").validate(); // Call to mark P1 as known - final PropertyValidator validator = factory.allCovered(); - final ValidationResult result = validator.validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-53: The following properties are unknown: 'P2', 'P3'" - + ". Please check the documentation of the adapter for valid properties and the spelling."))); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/EnumerationValidatorTest.java b/src/test/java/com/exasol/adapter/properties/EnumerationValidatorTest.java deleted file mode 100644 index 0c48de4..0000000 --- a/src/test/java/com/exasol/adapter/properties/EnumerationValidatorTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; - -class EnumerationValidatorTest extends AbstractPropertyValidatorTest { - private enum the_enum { - A, B, C - } - - // [utest -> dsn~validating-enumeration-properties~1] - @Test - void testWhenValueIsInEnumerationThenValidationSucceeds() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_ENUM", "A"); - final PropertyValidator validator = factory.enumeration("THE_ENUM", the_enum.class); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-enumeration-properties~1] - @Test - void testWhenValueIsNotInEnumerationThenValidationFails() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_ENUM", "D"); - final PropertyValidator validator = factory.enumeration("THE_ENUM", the_enum.class); - final ValidationResult result = validator.validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-44: The property 'THE_ENUM' has an invalid value 'D'." - + " Please pick one of the following values: 'A', 'B', 'C'"))); - } - - // [utest -> dsn~validating-enumeration-properties~1] - @Test - void testWhenValueIsNullThenValidationFails() { - final Map propertiesWithNull = new HashMap<>(); - propertiesWithNull.put("THE_ENUM", null); - final AdapterProperties properties = new AdapterProperties(propertiesWithNull); - final ValidatorFactory factory = ValidatorFactory.create(properties); - final PropertyValidator validator = factory.enumeration("THE_ENUM", the_enum.class); - final ValidationResult result = validator.validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-44: The property 'THE_ENUM' has an invalid value ." - + " Please pick one of the following values: 'A', 'B', 'C'"))); - } - - -} diff --git a/src/test/java/com/exasol/adapter/properties/ExasolObjectIdValidatorTest.java b/src/test/java/com/exasol/adapter/properties/ExasolObjectIdValidatorTest.java deleted file mode 100644 index ed00404..0000000 --- a/src/test/java/com/exasol/adapter/properties/ExasolObjectIdValidatorTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -class ExasolObjectIdValidatorTest extends AbstractPropertyValidatorTest { - @ValueSource(strings = { // - "A_valid_object_id", // - "anotherValidID1", // - "ÜnicodeMiddleDot·Test", // - "Valid_ID_with_Mn̼͡", // - }) - @ParameterizedTest - // [utest -> dsn~validating-exasol-object-id-properties~1] - void testWhenValidExasolObjectIdIsGivenThenValidationSucceeds(final String id) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("EXA_OBJECT_ID", id); - final PropertyValidator validator = factory.exasolObjectId("EXA_OBJECT_ID"); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - @ValueSource(strings = { // - "", // Empty string - "ID_with_unsupported&characters", // - "ID with spaces", // - "1ID_starting_with_a_number", // - "ID-ending-with-special-char!" // - }) - @ParameterizedTest - // [utest -> dsn~validating-exasol-object-id-properties~1] - void testWhenInvalidExasolObjectIdIsGivenThenValidationFails(final String id) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("EXA_OBJECT_ID", id); - final PropertyValidator validator = factory.exasolObjectId("EXA_OBJECT_ID"); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(false)); - } -} diff --git a/src/test/java/com/exasol/adapter/properties/IntegerValidatorTest.java b/src/test/java/com/exasol/adapter/properties/IntegerValidatorTest.java deleted file mode 100644 index 646be1d..0000000 --- a/src/test/java/com/exasol/adapter/properties/IntegerValidatorTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class IntegerValidatorTest extends AbstractPropertyValidatorTest { - @ValueSource(strings = { "" + Long.MIN_VALUE, "-1", "0", "1", "" + Long.MAX_VALUE }) - @ParameterizedTest - // [utest -> dsn~validating-integer-properties~1] - void testWhenValidIntegerIsGivenWithOutBoundariesThenValidationSucceeds(final String value) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_INTEGER", value); - final PropertyValidator validator = factory.integer("THE_INTEGER"); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - @ValueSource(strings = { "SOME STRING", "-1.0", "0.0", "1.0" }) - @ParameterizedTest - // [utest -> dsn~validating-integer-properties~1] - void testWhenInvalidIntegerIsGivenThenValidationFails(final String value) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_INTEGER", value); - final PropertyValidator validator = factory.integer("THE_INTEGER"); - final ValidationResult result = validator.validate(); - assertAll( // - () -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), equalTo("E-VSCOMJAVA-47: The value '" + value - + "' for property 'THE_INTEGER' is not a valid integer number."))); - } - - @CsvSource({ // - "0, 1, 2", // - "1, 1, 1", // - Long.MIN_VALUE + ", 1 ," + Long.MAX_VALUE, // - "-1, 0, 1" }) - @ParameterizedTest - // [utest -> dsn~integer-interval-validation~1] - void testWhenBoundariesAreProvidedAndValueIsBetweenBoundariesThenValidationSucceeds(final long min, - final String value, final long max) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_INTEGER", value); - final PropertyValidator validator = factory.integer("THE_INTEGER", min, max); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - @CsvSource({ // - "0, -1, 2", // - "1, 0, 1", // - (Long.MIN_VALUE + 1) + ", " + Long.MIN_VALUE + " ," + Long.MAX_VALUE, // - "-1, 2, 1" }) - @ParameterizedTest - // [utest -> dsn~integer-interval-validation~1] - void testWhenBoundariesAreProvidedAndValueIsOutsideBoundariesThenValidationFails(final long min, final String value, - final long max) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_INTEGER", value); - final PropertyValidator validator = factory.integer("THE_INTEGER", min, max); - final ValidationResult result = validator.validate(); - assertAll( // - () -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-46: The value for property 'THE_INTEGER' must be between " + min + " and " - + max + ", but was " + value + "."))); - } - - @Test - // [utest -> dsn~integer-interval-validation~1] - void testWhenSettingUpperBoundaryBelowLowerBoundaryThenExceptionIsThrown() { - final ValidatorFactory factory = createValidatorFactoryWithEmptyProperties(); - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> factory.integer("THE_INTEGER", 1, -1)); - assertThat(exception.getMessage(), startsWith( - "E-VSCOMJAVA-54: The upper bound (-1) must be greater than or equal the lower bound (1) for property 'THE_INTEGER'.")); - - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/MultiSelectValidatorTest.java b/src/test/java/com/exasol/adapter/properties/MultiSelectValidatorTest.java deleted file mode 100644 index 5241fee..0000000 --- a/src/test/java/com/exasol/adapter/properties/MultiSelectValidatorTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.exasol.adapter.properties; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.jupiter.params.provider.MethodSource; - -class MultiSelectValidatorTest extends AbstractPropertyValidatorTest { - private enum MULTI_SELECT_ENUM { - ONE, TWO, THREE, FOUR - } - - // [utest -> dsn~validating-multi-select-properties~1] - @ValueSource(strings = { - "ONE", - " ONE ", - "TWO", - "ONE,TWO", - "ONE, TWO", - "ONE, TWO", - "ONE\t,\tTWO", - "ONE, TWO, THREE" - }) - @ParameterizedTest - void testWhenKnownEnumValueIsGivenThenValidationSucceeds(final String propertyValue) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_MS_PROPERTY", propertyValue); - final PropertyValidator validator = factory.multiSelect("THE_MS_PROPERTY", MULTI_SELECT_ENUM.class); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - private static Stream unknownEnumValuesProvider() { - return Stream.of( - Arguments.of("FIVE", "'FIVE'"), - Arguments.of("one", "'one'"), - Arguments.of("ONETWO", "'ONETWO'"), - Arguments.of("ONE,TWO,SIX,SEVEN", "'SIX', 'SEVEN'")); - } - - // [utest -> dsn~validating-multi-select-properties~1] - @MethodSource("unknownEnumValuesProvider") - @ParameterizedTest - void testWhenUnknownEnumValueIsGivenThenValidationFails(final String propertyValue, final String reportedWrong) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_MS_PROPERTY", propertyValue); - final PropertyValidator validator = factory.multiSelect("THE_MS_PROPERTY", MULTI_SELECT_ENUM.class); - final ValidationResult result = validator.validate(); - assertAll( - () -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), equalTo( - "E-VSCOMJAVA-57: The following values given for the property 'THE_MS_PROPERTY' are unknown: " - + reportedWrong - + " Please use one or more of the following values: 'ONE', 'TWO', 'THREE', 'FOUR'." - + " Separate the individual values with a comma."))); - } - - // [utest -> dsn~validating-multi-select-properties~1] - @ValueSource(strings = { - "ONE", " ONE ", - "TWO", - "ONE,TWO", - "ONE, TWO", - "ONE, TWO", - "ONE\t,\tTWO", - "ONE, TWO, THREE", - // validate that blank single values and blank comma-separated values are accepted too - "", " ", " ", "\t", "\n\t", "\n", "", "", "", ",", " , , " - }) - @ParameterizedTest - void testWhenEmptyValueIsAllowedThenValidationOfEmptyAndNonEmptySucceeds(final String value) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("EMPTY_ALLOWED", value); - final PropertyValidator validator = factory.multiSelectEmptyAllowed("EMPTY_ALLOWED", MULTI_SELECT_ENUM.class); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-multi-select-properties~1] - @ValueSource(strings = { "", " ", " ", "\t", "\n\t", "\n", " , ", ",,", " , , " }) - @ParameterizedTest - void testWhenValueIsEmptyAndEmptyValueIsNotAllowedThenValidationFails(final String value) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("EMPTY_NOT_ALLOWED", value); - final PropertyValidator validator = factory.multiSelect("EMPTY_NOT_ALLOWED", MULTI_SELECT_ENUM.class); - final ValidationResult result = validator.validate(); - assertAll( - () -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), equalTo( - "E-VSCOMJAVA-56: The property 'EMPTY_NOT_ALLOWED' must have at least one value set." - + " Please select at least one of the following values: 'ONE', 'TWO', 'THREE', 'FOUR'." - + " Separate the individual values with a comma."))); - } - - // [utest -> dsn~validating-multi-select-properties~1] - @Test - void testWhenValueIsNullAndEmptyValueIsAllowedThenValidationSucceeds() { - final Map mapWithNull = new HashMap<>(1); - mapWithNull.put("NULL_ALLOWED", null); - final ValidatorFactory factory = ValidatorFactory.create(new AdapterProperties(mapWithNull)); - final PropertyValidator validator = factory.multiSelectEmptyAllowed("NULL_ALLOWED", MULTI_SELECT_ENUM.class); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-multi-select-properties~1] - @Test - void testWhenValueIsNullAndEmptyValueIsNotAllowedThenValidationFails() { - final Map mapWithNull = new HashMap<>(1); - mapWithNull.put("NULL_FORBIDDEN", null); - final ValidatorFactory factory = ValidatorFactory.create(new AdapterProperties(mapWithNull)); - final PropertyValidator validator = factory.multiSelect("NULL_FORBIDDEN", MULTI_SELECT_ENUM.class); - final ValidationResult result = validator.validate(); - assertAll( - () -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), equalTo( - "E-VSCOMJAVA-56: The property 'NULL_FORBIDDEN' must have at least one value set." - + " Please select at least one of the following values: 'ONE', 'TWO', 'THREE', 'FOUR'." - + " Separate the individual values with a comma."))); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/RequiredValidatorTest.java b/src/test/java/com/exasol/adapter/properties/RequiredValidatorTest.java deleted file mode 100644 index f3f472f..0000000 --- a/src/test/java/com/exasol/adapter/properties/RequiredValidatorTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Stream; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; - -class RequiredValidatorTest extends AbstractPropertyValidatorTest{ - - // [utest -> dsn~validating-the-existence-of-mandatory-properties~1] - @Test - void testWhenMandatoryPropertyIsPresentThenValidationSucceeds() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("MANDATORY_PROPERTY", "present"); - final PropertyValidator validator = factory.required("MANDATORY_PROPERTY"); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-the-existence-of-mandatory-properties~1] - @ParameterizedTest - @MethodSource("variationsWithMissingProperty") - void testWhenMandatoryPropertyIsMissingThenValidationFails(Map properties) { - final ValidatorFactory factory = createValidatorFactoryWithProperties(properties); - final PropertyValidator validator = factory.required("MANDATORY_PROPERTY"); - final ValidationResult result = validator.validate(); - assertAll( // - () -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-48: The mandatory property 'MANDATORY_PROPERTY' is missing."))); - } - - private static Stream variationsWithMissingProperty() { - final Map propertyWithNull = new HashMap<>(); - propertyWithNull.put("MANDATORY_PROPERTY", null); - return Stream.of(Arguments.of(Collections.emptyMap()), Arguments.of(propertyWithNull)); - } - - // [utest -> dsn~validating-the-existence-of-mandatory-properties~1] - @Test - void testWhenMandatoryPropertyIsEmptyThenValidationFails() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("EMPTY_PROPERTY", ""); - final PropertyValidator validator = factory.required("EMPTY_PROPERTY"); - final ValidationResult result = validator.validate(); - assertAll( // - () -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-49: The mandatory property 'EMPTY_PROPERTY' is empty."))); - } - - // [utest -> dsn~validating-the-absence-of-unwanted-properties~1] - @Test - void testWhenAnUnwantedPropertyIsSetThenValidationFails() { - final ValidatorFactory factory = createValidatorFactoryWithProperties("UNWANTED_PROPERTY", "present"); - final PropertyValidator validator = factory.unwanted("UNWANTED_PROPERTY"); - final ValidationResult result = validator.validate(); - assertAll( // - () -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), equalTo( - "E-VSCOMJAVA-50: The unwanted property 'UNWANTED_PROPERTY' is set. Please remove the property."))); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/StringValidatorTest.java b/src/test/java/com/exasol/adapter/properties/StringValidatorTest.java deleted file mode 100644 index 7b95038..0000000 --- a/src/test/java/com/exasol/adapter/properties/StringValidatorTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import java.util.regex.Pattern; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -class StringValidatorTest extends AbstractPropertyValidatorTest { - // [utest -> dsn~validating-a-string-against-a-regular-expression~1] - @CsvSource({ // - "1, \\d", // - "1234567890, \\d{10}", // - "A:1, \\w:\\d" // - }) - @ParameterizedTest - void testWhenThePropertyValueMatchesTheRegExThenValidationSucceeds(final String value, final String regEx) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_STRING", value); - final StringValidator validator = factory.matches("THE_STRING", Pattern.compile(regEx)); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-a-string-against-a-regular-expression~1] - @CsvSource({ // - "1, \\d{2}", // - "12345, \\d{10}", // - "A-1, \\w:\\d" // - }) - @ParameterizedTest - void testWhenThePropertyValueDoesNotMatchTheRegExThenValidationFails(final String value, final String regEx) { - final Pattern pattern = Pattern.compile(regEx); - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_STRING", value); - final StringValidator validator = factory.matches("THE_STRING", pattern); - final ValidationResult result = validator.validate(); - assertAll( - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-51: The property 'THE_STRING' has an invalid value '" + value - + "'. Please use a value matching the regular expression '" + pattern + "'.")), - () -> assertThat(result.isValid(), equalTo(false))); - } - - // [utest -> dsn~reporting-format-violations-in-properties~1] - @Test - void testWhenAnExplanationForFormatIsGivenThenThisIsUsedInTheErrorMessageInsteadOfPattern() { - final Pattern pattern = Pattern.compile("^[^:]+:\\d+$"); - final ValidatorFactory factory = createValidatorFactoryWithProperties("ADDRESS", "server.example.com:8080X"); - final StringValidator validator = factory.matches("ADDRESS", pattern, ":"); - final ValidationResult result = validator.validate(); - assertAll( - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-55: The property 'ADDRESS' has an invalid value 'server.example.com:8080X'" - + ". Please use the format ':'.")), - () -> assertThat(result.isValid(), equalTo(false))); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/UnixPathValidatorTest.java b/src/test/java/com/exasol/adapter/properties/UnixPathValidatorTest.java deleted file mode 100644 index 57c3e88..0000000 --- a/src/test/java/com/exasol/adapter/properties/UnixPathValidatorTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; - -// Since the production code only runs on a Unix-like OS, we run the unit test -// only on OSes that use Unix filesystem conventions. -@EnabledOnOs({ OS.LINUX, OS.FREEBSD, OS.OPENBSD, OS.MAC, OS.SOLARIS, OS.AIX }) -class UnixPathValidatorTest extends AbstractPropertyValidatorTest { - // [utest -> dsn~validating-unix-paths~1] - @ValueSource(strings = { - "/a", // - "/c.conf", // - "/.foo", // - "/a/b/c.conf", // - "/a/b/c.conf", // - "/a/b/.foo", // - }) - @ParameterizedTest - void testWhenGivenProperUnixPathThenValidationIsSuccessful(final String path) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_PATH", path); - final UnixPathValidator validator = factory.absolutePath("THE_PATH"); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(true)); - } - - // [utest -> dsn~validating-unix-paths~1] - // [utest -> dsn~only-absolute-paths-are-valid-in-properties~1] - @ValueSource(strings = { // - "relative/path", // - "///", // - " ", // - "./invalid.path", // - "../invalid.path", // - "/invalid.path/.", // - "/invalid.path/..", // - "/sorry/../no/", // - "/does/./not/work", // - "/this//also/does/not", // - "/foo%2E%2E%2Fbar", // - "/foo\tbar", // - "/foo\nbar", // - "/foo\rbar", // - "/foo\\bar", // - "/foo\u0000bar", // - "/foo/.\u0000./bar", // - "/\u0000.\u0000./bar", // - "/foo/\u002E./bar", // - "/foo/\u002E\u002E/bar", // - "/end/with/dot/.", // - "/:", // - "http://foo/bar", // - "ftp://foo/bar", // - "file://foo/bar" // - }) - @ParameterizedTest - void testWhenGivenInvalidUnixPathThenValidationFails(final String path) { - final ValidatorFactory factory = createValidatorFactoryWithProperties("THE_PATH", path); - final UnixPathValidator validator = factory.absolutePath("THE_PATH"); - final ValidationResult result = validator.validate(); - assertAll( - () -> assertThat(result.isValid(), equalTo(false)), - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-59: The property 'THE_PATH' contains an invalid path: '" - + path + - "'. Known mitigations:\n" - + "* Make sure you provide an absolute path.\n" - + "* Don't use a protocol specifier (like 'http:').\n" - + "* Avoid blank paths, tabs, newlines and carriage returns.\n" - + "* Please remove any kind of path traversal characters ('.', '..', '//')."))); - } -} diff --git a/src/test/java/com/exasol/adapter/properties/ValidationContextTest.java b/src/test/java/com/exasol/adapter/properties/ValidationContextTest.java deleted file mode 100644 index 5cc41c9..0000000 --- a/src/test/java/com/exasol/adapter/properties/ValidationContextTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.sameInstance; - -class ValidationContextTest { - @Test - void testConstructingContext() { - final AdapterProperties properties = AdapterProperties.emptyProperties(); - final ValidationContext context = new ValidationContext(properties); - assertThat(context.getProperties(), sameInstance(properties)); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/properties/ValidationLogTest.java b/src/test/java/com/exasol/adapter/properties/ValidationLogTest.java deleted file mode 100644 index d54ca24..0000000 --- a/src/test/java/com/exasol/adapter/properties/ValidationLogTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; - -class ValidationLogTest { - @Test - void testWhenPropertyWasValidatedThenLogReportsItAsValidated() { - final ValidationLog log = new ValidationLog(); - log.addValidation("THE_PROPERTY"); - assertThat(log.isValidated("THE_PROPERTY"), equalTo(true)); - } - - @Test - void testWhenPropertyWasNotValidatedThenLogReportsItAsNotValidated() { - final ValidationLog log = new ValidationLog(); - log.addValidation("THE_PROPERTY"); - assertThat(log.isValidated("THE_OTHER_PROPERTY"), equalTo(false)); - } - - @Test - void testGettingValidatedProperties() { - final ValidationLog log = new ValidationLog(); - log.addValidation("P1"); - log.addValidation("P2"); - assertThat(log.getValidatedProperties(), contains("P1", "P2")); - } -} diff --git a/src/test/java/com/exasol/adapter/properties/ValidatorFactoryTest.java b/src/test/java/com/exasol/adapter/properties/ValidatorFactoryTest.java deleted file mode 100644 index 59c8468..0000000 --- a/src/test/java/com/exasol/adapter/properties/ValidatorFactoryTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.exasol.adapter.properties; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Map; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; - -/** - * Unit tests for {@link ValidatorFactory}. - * - *

- * Verifies combinations of property validators in different scenarios by testing their interaction and validation - * results. - *

- *

- * Please note that the tests for the individual factory methods are already covered in the unit tests for the - * individual validators. - *

- */ -// [utest -> dsn~validator-composition~1] -class ValidatorFactoryTest { - private static Arguments testCase(final boolean expectedResult, final String... tuples) { - Map properties = Stream.of(tuples).map(tuple -> tuple.split("=")) - .collect(Collectors.toMap(split -> split[0], split -> split[1])); - return Arguments.of(expectedResult, properties); - } - - private static Stream generateCombinationTestData() { - return Stream.of( // - testCase(true, "BOOLEAN_PROPERTY=true"), testCase(false, "BOOLEAN_PROPERTY=illegal value"), - testCase(false), testCase(false, "UNEXPECTED_BOOLEAN_PROPERTY=true")); - } - - @MethodSource("generateCombinationTestData") - @ParameterizedTest - void testCombinationOfValidators(final boolean expectedResult, final Map properties) { - final ValidatorFactory v = ValidatorFactory.create(new AdapterProperties(properties)); - final PropertyValidator validator = // - v.and( // - v.required("BOOLEAN_PROPERTY"), // - v.bool("BOOLEAN_PROPERTY"), // - v.allCovered() // - ); - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(expectedResult)); - } - - public static Stream generateTreeTestData() { - return Stream.of( // - testCase(true, "REQ_BASE_BOOL=true", "REQ_BASE_STRING=a1", "REQ_DIALECT_INT=1", - "OPT_DIALECT_BOOL=true"), - testCase(true, "REQ_BASE_BOOL=true", "REQ_BASE_STRING=a1", "REQ_DIALECT_INT=1", - "OPT_DIALECT_BOOL=true"), - testCase(false, "REQ_BASE_BOOL=true", "REQ_BASE_STRING=a1", "REQ_DIALECT_INT=1", - "OPT_DIALECT_BOOL=true", "UNEXPECTED_PROPERTY=true"), - testCase(false), // - testCase(false, "REQ_BASE_BOOL=true", "REQ_BASE_STRING=a1", "REQ_DIALECT_INT=invalid_value") // - ); - } - - @MethodSource("generateTreeTestData") - @ParameterizedTest - void testConstructingValidatorTree(final boolean expectedResult, final Map properties) { - final ValidatorFactory v = ValidatorFactory.create(new AdapterProperties(properties)); - final PropertyValidator validator = // - v.allOf( // - v.allOf( // - v.and( // - v.required("REQ_BASE_BOOL"), // - v.bool("REQ_BASE_BOOL") // - ), // - v.and( // - v.required("REQ_BASE_STRING"), // - v.matches("REQ_BASE_STRING", Pattern.compile("\\w\\d")) // - ) // - ), // - v.and( // - v.required("REQ_DIALECT_INT"), // - v.integer("REQ_DIALECT_INT") // - ), // - v.bool("OPT_DIALECT_BOOL"), // - v.allCovered()); // - final ValidationResult result = validator.validate(); - assertThat(result.isValid(), equalTo(expectedResult)); - } - - @Test - void testRequiredShortHandForm() { - final ValidatorFactory v = ValidatorFactory.create(AdapterProperties.emptyProperties()); - final PropertyValidator validator = v.required( // - v.bool("P1") // - ); - final ValidationResult result = validator.validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), equalTo("E-VSCOMJAVA-48: The mandatory property 'P1' is missing."))); - } - - @Test - void testWhenRequiredIsCombinedWithUnwantedAndAllCoveredThenUnwantedIsHandledCorrectly() { - final ValidatorFactory v = ValidatorFactory.create(new AdapterProperties(Map.of("P1", "A", "P2", "B"))); - final PropertyValidator validator = v.allOf( // - v.required("P1"), // - v.unwanted("P2"), // - v.allCovered()); - final ValidationResult result = validator.validate(); - assertAll(() -> assertThat(result.isValid(), equalTo(false)), // - () -> assertThat(result.getMessage(), - equalTo("E-VSCOMJAVA-50: The unwanted property 'P2' is set. Please remove the property.")) - - ); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/request/LoggingConfigurationTest.java b/src/test/java/com/exasol/adapter/request/LoggingConfigurationTest.java index 6c95e4b..b4165f3 100644 --- a/src/test/java/com/exasol/adapter/request/LoggingConfigurationTest.java +++ b/src/test/java/com/exasol/adapter/request/LoggingConfigurationTest.java @@ -1,7 +1,7 @@ package com.exasol.adapter.request; -import static com.exasol.adapter.properties.AdapterProperties.DEBUG_ADDRESS_PROPERTY; -import static com.exasol.adapter.properties.AdapterProperties.LOG_LEVEL_PROPERTY; +import static com.exasol.adapter.AdapterProperties.DEBUG_ADDRESS_PROPERTY; +import static com.exasol.adapter.AdapterProperties.LOG_LEVEL_PROPERTY; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertAll;